JavassistModuleHelper.java
/*
* Copyright (C) 2015-2016 Federico Tomassetti
* Copyright (C) 2017-2025 The JavaParser Team.
*
* This file is part of JavaParser.
*
* JavaParser can be used either under the terms of
* a) the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* b) the terms of the Apache License
*
* You should have received a copy of both licenses in LICENCE.LGPL and
* LICENCE.APACHE. Please refer to those files for details.
*
* JavaParser is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*/
package com.github.javaparser.symbolsolver.utils;
import com.github.javaparser.utils.Pair;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import javassist.CtClass;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
public class JavassistModuleHelper {
public static String MODULE_INFO_CLASS_NAME = "module-info";
/**
* Javassist does not provide support for modules beyond letting users fetch the module
* attribute byte array, so the attribute needs to be parsed manually. This is done
* according to <a href="https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.7.25">The JVM Spec</a>
* for the module attribute.
*
* @param moduleInfo the CtClass for module-info.class
* @return a pair of [ModuleName, ExportedPackageNames] if the module attribute is not empty.
*/
public static Optional<Pair<String, List<String>>> getModuleWithExportedPackages(CtClass moduleInfo) {
ClassFile classFile = moduleInfo.getClassFile();
if (!classFile.getName().equals(MODULE_INFO_CLASS_NAME)) {
throw new IllegalArgumentException("getModuleWithExportedPackages only supports module-info.class");
}
ConstPool constPool = classFile.getConstPool();
byte[] moduleAttribute = classFile.getAttribute("Module").get();
// The first 2 bytes give the module_name_index, which is needed to get the module name from the constPool
int attrIdx = 0;
int moduleNameIndex = moduleAttribute[attrIdx++] << 16;
moduleNameIndex |= moduleAttribute[attrIdx++];
String moduleName = constPool.getModuleInfo(moduleNameIndex);
ArrayList<String> exportedPackages = new ArrayList<>();
// The next 4 bytes consist of the module_flags and module_version_index, neither of which
// are relevant here.
attrIdx += 4;
// The next 2 bytes are the requires_count
int requiresCount = moduleAttribute[attrIdx++] << 16;
requiresCount |= moduleAttribute[attrIdx++];
// Skip the requires table. Each require structure consists of 6 bytes.
attrIdx += requiresCount * 6;
// The next 2 bytes are the exports count
int exportsCount = moduleAttribute[attrIdx++] << 16;
exportsCount |= moduleAttribute[attrIdx++];
for (int i = 0; i < exportsCount; i++) {
int exportsIndex = moduleAttribute[attrIdx++] << 16;
exportsIndex = moduleAttribute[attrIdx++];
String exportedPackageName = constPool.getPackageInfo(exportsIndex).replace('/', '.');
exportedPackages.add(exportedPackageName);
// Skip the 2 byte exports_flags
attrIdx += 2;
// The next 2 bytes are the exports to count. Need this to skip the exports to table for now, but
// could use them for better resolution.
int exportsToCount = moduleAttribute[attrIdx++] << 16;
exportsToCount |= moduleAttribute[attrIdx++];
// TODO Eventually check exportedTo to see if this is valid
// For now, skip each 2 byte exports_to
attrIdx += 2 + exportsToCount;
}
return Optional.of(new Pair<>(moduleName, exportedPackages));
}
}