Although you haven't mentioned it explicitly I think you are running a version of Java with modules (JDK 9+), but the guides you have been following are for earlier versions starting from Java 6. This is why you are getting the error about unsupported listLocationsForModules
because the JDK developers retrofitted the FileManager
with with default methods that throw UnsupportedOperationException
.
If you don't actually want to use a version of Java greater than 8, I would stick with JDK8 it will be much easier!
I'll continue assuming you do want to use Java 9 and above (tested my code in Java 11) however:
For handling modules it is sufficient your file manager to delegate to the standard file manager:
@Override
public Location getLocationForModule(Location location, String moduleName) throws IOException {
return standardFileManager.getLocationForModule(location, moduleName);
}
@Override
public Location getLocationForModule(Location location, JavaFileObject fo) throws IOException {
return standardFileManager.getLocationForModule(location, fo);
}
@Override
public Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
return standardFileManager.listLocationsForModules(location);
}
@Override
public String inferModuleName(Location location) throws IOException {
return standardFileManager.inferModuleName(location);
}
I also found it necessary to modify Atamur's code to check explicitly for the base java module (so that we can resolve java.lang in Java 9+!) and delegate to the standard file manager as you would have for the platform class path in previous versions:
@Override
public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException {
boolean baseModule = location.getName().equals("SYSTEM_MODULES[java.base]");
if (baseModule || location == StandardLocation.PLATFORM_CLASS_PATH) { // **MODIFICATION CHECK FOR BASE MODULE**
return standardFileManager.list(location, packageName, kinds, recurse);
} else if (location == StandardLocation.CLASS_PATH && kinds.contains(JavaFileObject.Kind.CLASS)) {
if (packageName.startsWith("java") || packageName.startsWith("com.sun")) {
return standardFileManager.list(location, packageName, kinds, recurse);
} else { // app specific classes are here
return finder.find(packageName);
}
}
return Collections.emptyList();
}
Updates
Some other points:
Extracting embedded spring boot classes:
Get the jarUri by looking for the last index of '!' in each packageFolderURL as in Taeyun Kim's comment and not the first as in the original example.
private List<JavaFileObject> processJar(URL packageFolderURL) {
List<JavaFileObject> result = new ArrayList<JavaFileObject>();
try {
// Replace:
// String jarUri = packageFolderURL.toExternalForm().split("!")[0];
// With:
String externalForm = packageFolderURL.toExternalForm();
String jarUri = externalForm.substring(0, externalForm.lastIndexOf('!'));
JarURLConnection jarConn = (JarURLConnection) packageFolderURL.openConnection();
String rootEntryName = jarConn.getEntryName();
int rootEnd = rootEntryName.length()+1;
// ...
This allows package PackageInternalsFinder
to return CustomJavaFileObject
with full URIs to classes in embedded spring jars (under BOOT-INF/lib
) which are then resolved with spring boot jar URI handler which is registered in similar way to as explained in this answer. The URI handling should just happen automatically through spring boot.