My Question:
Is it possible to get a Uri from an import of a third party dependency?
Question Context:
I am trying to get a list of classes accessed with the following.
import com.name.*
In the context in which I want to use it, I will not be able to use third party dependencies. I do however need to find all classes associated with a third party dependency import.
I have found one such answer to my issue in the following code, provided by the user tirz.
public static List<Class<?>> getClassesForPackage(final String pkgName) throws IOException, URISyntaxException {
final String pkgPath = pkgName.replace('.', '/');
final URI pkg = Objects.requireNonNull(ClassLoader.getSystemClassLoader().getResource(pkgPath)).toURI();
final ArrayList<Class<?>> allClasses = new ArrayList<Class<?>>();
Path root;
if (pkg.toString().startsWith("jar:")) {
try {
root = FileSystems.getFileSystem(pkg).getPath(pkgPath);
} catch (final FileSystemNotFoundException e) {
root = FileSystems.newFileSystem(pkg, Collections.emptyMap()).getPath(pkgPath);
}
} else {
root = Paths.get(pkg);
}
final String extension = ".class";
try (final Stream<Path> allPaths = Files.walk(root)) {
allPaths.filter(Files::isRegularFile).forEach(file -> {
try {
final String path = file.toString().replace('/', '.');
final String name = path.substring(path.indexOf(pkgName), path.length() - extension.length());
allClasses.add(Class.forName(name));
} catch (final ClassNotFoundException | StringIndexOutOfBoundsException ignored) {
}
});
}
return allClasses;
}
The problem I have with the code above is that where final URI pkg is assigned. This works with a package that exists within the project, but if an import for a third party dependency is used this throws a NullPointerException. Is it possible to make this code work for third party dependencies? Might this require some reference to an .m2 folder or other library resource?
I have two java projects MASTER and PLUGIN. PLUGIN has dependencies to MASTER and its intent is to extend a class found in MASTER, called SCRIPT.
Once I have declared a SCRIPT (myScript), I want to move the .class file to a folder that MASTER can access. I want MASTER to dynamically load and instantiate that class as a SCRIPT.
I've looked for quite a bit and tried different solutions, but I always get a ClassNotFoundException exception.
I would prefer to do this without passing arguments to the JVM at startup.
Is it even possible? This is my current solution: "currentPath" is "etc/etc/myScript.class
try {
OUT.ln("initiating script " + currentPath);
File file = new File(currentPath);
File parent = file.getParentFile();
String name = file.getName().split(".class")[0];
// Convert File to a URL
URL url = parent.toURI().toURL();
URL[] urls = new URL[]{url};
// Create a new class loader with the directory
#SuppressWarnings("resource")
ClassLoader cl = new URLClassLoader(urls);
current = (SCRIPT) cl.loadClass("main.script." + name).newInstance();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Unable to load script " + currentPath);
}
if the class you want to load is defined within a package like:
main.script.myScript
and you want to load this class from a folder like c:/myclasses,
then you have to put this class to c:/myclasses/main/script/myScript.class
and then instantate the classloader with the basefolder like:
URL[] urls = new URL[]{new URL("file://c:/myclasses")};
ClassLoader cl = new URLClassLoader(urls);
then the class can be loaded by using the qualified class name:
cl.loadClass("main.script.myScript").getDeclaredConstructor().newInstance()
if you want to keep the class at a specific folder without considering the package structure you could do something like this:
public static void main(String[] args) {
try {
File file = new File("etc/etc/myScript.class");
String className = file.getName().split(".class")[0];
String packageName = "main.script.";
byte[] bytes = Files.readAllBytes(Path.of(file.getPath()));
MyClassLoader myClassLoader = new MyClassLoader(Thread.currentThread().getContextClassLoader());
Object o = myClassLoader.getClass(packageName+className, bytes).getDeclaredConstructor().newInstance();
System.out.println(o);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Unable to load script ");
}
}
public static class MyClassLoader extends ClassLoader {
public MyClassLoader(ClassLoader parent) {
super(parent);
}
public Class<?> getClass(String name, byte[] code) {
return defineClass(name, code, 0, code.length);
}
}
I am developing a wicket application which is running on jetty. My application should be able to load plugins from jar-files.
Thus I created a URLClassLoader which should load the plugin classes.
As a standard Java-application everything works, but when I want to load plugins on the server I get following errors: http://pastebin.com/e2JRAYTr
The Module interface that can not be found is defined in another project, but if I output the class-name in Wickets init() method like this
System.out.println(Module.class.getName())`
I get no error. So I think the other project is loaded correctly by Maven but there are issues with my custom jar-classloader.
The classloaders constructor where the action happens looks like this:
public WorkableLoader(final File jarFile) throws IOException, ClassNotFoundException, InstantiationException,
IllegalAccessException {
final URL[] urls = new URL[] { jarFile.toURI().toURL() };
classLoader = new URLClassLoader(urls);
final Class<?> cls = loadWorkableClass(jarFile);
workable = (Workable) cls.newInstance();
}
private Class<?> loadWorkableClass(final File jar) throws ClassNotFoundException, IOException {
try (final JarFile jarFile = new JarFile(jar)) {
final Enumeration<JarEntry> jarEntries = jarFile.entries();
while (jarEntries.hasMoreElements()) {
final JarEntry jarEntry = jarEntries.nextElement();
if (isClassFile(jarEntry.getName())) {
final String className = jarEntry.getName().replace("/", ".").replace(".class", "");
final Class<?> cls = classLoader.loadClass(className);
for (final Class<?> iface : cls.getInterfaces()) {
if (iface.equals(Module.class) || iface.equals(Bundle.class)) {
return cls;
}
}
}
}
} catch (final IOException e) {
throw e;
}
throw new ClassNotFoundException("Es konnte keine Workable-Klasse gefunden werden.");
}
The method isClass() just checks if the file ends with .class.
The last error of my code is at com.siemens.importer.workables.WorkableLoader.loadWorkableClass(WorkableLoader.java:137). Line 137 is the line
final Class<?> cls = classLoader.loadClass(className); of the loadWorkableClass() method. So how can I load classes with a custom classloader on a
jetty server?
Sorry for the huge output and thanks in advance!
I have an unusual problem which is concerned to dynamic loading java .class file at run-time. All I want to do is to load a .class file and basing on it create a Class object.
Input: an absolute path of .class file.
Basing on it i want to load class by ClassLoader, so I need a path of root directory where file is located and full class name e.g com.test.MyClass. Basing on mentioned absolute path I can only get a class name but I can't get a package name which is "hiden" in this file.
Here is code of my "loading class method":
public static void loadClass(String directory){
// Get file root directory
String rootDirectory = new File(directory).getParent();
// Get rid of file extension
String className = getFileNameWithoutExtension(directory);
URL[] urls = null;
ClassLoader cl = null;
try {
// Convert File to a URL and save them
urls = new URL[]{new File(rootDirectory).toURI().toURL()};
// Create a new class loader with the directory
cl = new URLClassLoader(urls);
// Load in the class
dynamicClass = cl.loadClass(className);
}
catch (MalformedURLException e)
{
}
catch (ClassNotFoundException e)
{
}
catch (NoClassDefFoundError e)
{
// Basing on error message get the class package name
String classPackage = getClassPackage(e.getMessage());
try {
// Load the class once more!
dynamicClass = cl.loadClass(classPackage);
}
catch (ClassNotFoundException ex)
{
}
}
}
Second method is used to get package name from exception message:
private static String getClassPackage(String errorMsg){
// Start and end index of cutting
int startIndex = errorMsg.lastIndexOf(" ") + 1;
int endIndex = errorMsg.length() - 1;
// Let's save a substring
String classPackage = errorMsg.substring(startIndex, endIndex);
// Replace char '/' to '.'
classPackage = classPackage.replace('/', '.');
return classPackage;
}
Code of method getFileNameWithoutExtension:
private static String getFileNameWithoutExtension(String path){
int start = path.lastIndexOf(File.separator) + 1;
int end = path.lastIndexOf(DOT);
end = start < end ? end : path.length();
String name = path.substring(start, end);
return name;
}
Where the static final variable is:
private static final String DOT = ".";
And here is my question: is it possible to get package name from .class file without using this kind of trick?
You can use the Foo.class.getPackage().getName() method to determine this.
public Package getPackage()
Returns:
the package of the class, or null if no package information is available from the archive or codebase.
Using getName() :
public String getName()
Returns:
The fully-qualified name of this package as defined in section 6.5.3 of The Java™ Language Specification, for example, java.lang
Since you already have the required data in className, just use it again. You don't need the getClassPackage method.
catch (NoClassDefFoundError e)
{
// Basing on error message get the class package name
//But we already have the class name in className variable!
//String classPackage = getClassPackage(e.getMessage());
try {
// Load the class once more!
dynamicClass = cl.loadClass(className);
}
catch (ClassNotFoundException ex)
{
}
}
And if you want to get the package name only (not sure why), you can just get it from the class name:
String packageName = className.substring(0, className.lastIndexOf('.'));
dynamicClass = cl.loadClass(packageName);
You can do like this-
String packName = new Object(){}.getClass().getPackage().getName();
System.out.println(packName);
Lets say I have a java package commands which contains classes that all inherit from ICommand can I get all of those classes somehow? I'm locking for something among the lines of:
Package p = Package.getPackage("commands");
Class<ICommand>[] c = p.getAllPackagedClasses(); //not real
Is something like that possible?
Here's a basic example, assuming that classes are not JAR-packaged:
// Prepare.
String packageName = "com.example.commands";
List<Class<ICommand>> commands = new ArrayList<Class<ICommand>>();
URL root = Thread.currentThread().getContextClassLoader().getResource(packageName.replace(".", "/"));
// Filter .class files.
File[] files = new File(root.getFile()).listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(".class");
}
});
// Find classes implementing ICommand.
for (File file : files) {
String className = file.getName().replaceAll(".class$", "");
Class<?> cls = Class.forName(packageName + "." + className);
if (ICommand.class.isAssignableFrom(cls)) {
commands.add((Class<ICommand>) cls);
}
}
Below is an implementation using the JSR-199 API, i.e. classes from javax.tools.*:
List<Class> commands = new ArrayList<>();
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(
null, null, null);
StandardLocation location = StandardLocation.CLASS_PATH;
String packageName = "commands";
Set<JavaFileObject.Kind> kinds = new HashSet<>();
kinds.add(JavaFileObject.Kind.CLASS);
boolean recurse = false;
Iterable<JavaFileObject> list = fileManager.list(location, packageName,
kinds, recurse);
for (JavaFileObject classFile : list) {
String name = classFile.getName().replaceAll(".*/|[.]class.*","");
commands.add(Class.forName(packageName + "." + name));
}
Works for all packages and classes on the class path, packaged in jar files or without. For classes not explicitly added to the class path, i.e. those loaded by the bootstrap class loader, try setting location to PLATFORM_CLASS_PATH instead.
Here is an utility method, using Spring.
Details about the pattern can be found here
public static List<Class> listMatchingClasses(String matchPattern) throws IOException {
List<Class> classes = new LinkedList<Class>();
PathMatchingResourcePatternResolver scanner = new PathMatchingResourcePatternResolver();
Resource[] resources = scanner.getResources(matchPattern);
for (Resource resource : resources) {
Class<?> clazz = getClassFromResource(resource);
classes.add(clazz);
}
return classes;
}
public static Class getClassFromResource(Resource resource) {
try {
String resourceUri = resource.getURI().toString();
resourceUri = resourceUri.replace(esourceUri.indexOf(".class"), "").replace("/", ".");
// try printing the resourceUri before calling forName, to see if it is OK.
return Class.forName(resourceUri);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
If you do not want to use external depencies and you want to work on your IDE / on a JAR file, you can try this:
public static List<Class<?>> getClassesForPackage(final String pkgName) throws IOException, URISyntaxException {
final String pkgPath = pkgName.replace('.', '/');
final URI pkg = Objects.requireNonNull(ClassLoader.getSystemClassLoader().getResource(pkgPath)).toURI();
final ArrayList<Class<?>> allClasses = new ArrayList<Class<?>>();
Path root;
if (pkg.toString().startsWith("jar:")) {
try {
root = FileSystems.getFileSystem(pkg).getPath(pkgPath);
} catch (final FileSystemNotFoundException e) {
root = FileSystems.newFileSystem(pkg, Collections.emptyMap()).getPath(pkgPath);
}
} else {
root = Paths.get(pkg);
}
final String extension = ".class";
try (final Stream<Path> allPaths = Files.walk(root)) {
allPaths.filter(Files::isRegularFile).forEach(file -> {
try {
final String path = file.toString().replace('/', '.');
final String name = path.substring(path.indexOf(pkgName), path.length() - extension.length());
allClasses.add(Class.forName(name));
} catch (final ClassNotFoundException | StringIndexOutOfBoundsException ignored) {
}
});
}
return allClasses;
}
From: Can you find all classes in a package using reflection?
Start with public Classloader.getResources(String name). Ask the classloader for a class corresponding to each name in the package you are interested. Repeat for all classloaders of relevance.
Yes but its not the easiest thing to do. There are lots of issues with this. Not all of the classes are easy to find. Some classes could be in a: Jar, as a class file, over the network etc.
Take a look at this thread.
To make sure they were the ICommand type then you would have to use reflection to check for the inheriting class.
This would be a very useful tool we need, and JDK should provide some support.
But it's probably better done during build. You know where all your class files are and you can inspect them statically and build a graph. At runtime you can query this graph to get all subtypes. This requires more work, but I believe it really belongs to the build process.
Using Johannes Link's ClasspathSuite, I was able to do it like this:
import org.junit.extensions.cpsuite.ClassTester;
import org.junit.extensions.cpsuite.ClasspathClassesFinder;
public static List<Class<?>> getClasses(final Package pkg, final boolean includeChildPackages) {
return new ClasspathClassesFinder(new ClassTester() {
#Override public boolean searchInJars() { return true; }
#Override public boolean acceptInnerClass() { return false; }
#Override public boolean acceptClassName(String name) {
return name.startsWith(pkg.getName()) && (includeChildPackages || name.indexOf(".", pkg.getName().length()) != -1);
}
#Override public boolean acceptClass(Class<?> c) { return true; }
}, System.getProperty("java.class.path")).find();
}
The ClasspathClassesFinder looks for class files and jars in the system classpath.
In your specific case, you could modify acceptClass like this:
#Override public boolean acceptClass(Class<?> c) {
return ICommand.class.isAssignableFrom(c);
}
One thing to note: be careful what you return in acceptClassName, as the next thing ClasspathClassesFinder does is to load the class and call acceptClass. If acceptClassName always return true, you'll end up loading every class in the classpath and that may cause an OutOfMemoryError.
You could use OpenPojo and do this:
final List<PojoClass> pojoClasses = PojoClassFactory.getPojoClassesRecursively("my.package.path", null);
Then you can go over the list and perform any functionality you desire.