I'm attempting to use reflection to load a custom object (Rod) from a jar file. I have managed to get it to find the jar file and scan the class for the needed annotation, but whenever I call classLoader.loadClass() I get a ClassNotFoundException for the class that the class I'm attempting to load extends.
This is the code for the ClassLoader:
public static Set<Rod> getRods(File rodDirectory) throws Exception {
rodDirectory.mkdir();
URLClassLoader classLoader;
Set<Rod> rods = new HashSet<Rod>();
for (File f : rodDirectory.listFiles()) {
if (f.isDirectory() || !f.getName().endsWith(".jar"))
continue;
JarFile jar = new JarFile(f);
Enumeration<JarEntry> e = jar.entries();
classLoader = URLClassLoader.newInstance(new URL[]{new URL("jar:file:" + f.getAbsolutePath() + "!/")});
while (e.hasMoreElements()) {
JarEntry j = (JarEntry) e.nextElement();
if(j.isDirectory() || !j.getName().endsWith(".class")){
continue;
}
Class<?> c = classLoader.loadClass(j.getName().substring(0, j.getName().length() - 6));
CustomRod a = c.getAnnotation(CustomRod.class);
if (a == null)
continue;
if (a.minimumVersion() < RodsTwo.getVersion())
continue;
rods.add((Rod) c.getConstructor().newInstance());
}
jar.close();
}
return rods;
}
This is the code inside the jar I'm attempting to load:
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import ca.kanoa.RodsTwo.Objects.ConfigOptions;
import ca.kanoa.RodsTwo.Objects.CustomRod;
import ca.kanoa.RodsTwo.Objects.Rod;
#CustomRod(minimumVersion=1.001)
public class Test extends Rod {
public Test() throws Exception {
super("Test", 1, 46, new ConfigOptions(), 200);
}
#Override
public boolean run(Player arg0, ConfigurationSection arg1) {
arg0.sendMessage("HI!");
return true;
}
}
And all my other code can by found here.
I've just started playing around with reflection and any help with be awesome!
There can be other classes in the JAR, which a custom subclass needs, but you are only picking out the one class file.
For example, say someone creates AbstractCustomRod and then MyCustomRod which extends it and puts them both in the JAR file? Your code will skip over AbstractCustomRod, and MyCustomRod cannot be loaded.
If you want to specify that there can be no inheritance other than from Rod, and no helper classes in the JAR file, then the only value JARs add is that they could be signed - if you don't care about that, you might as well receive plain .class files, which guarantees it's exactly one class.
Depending on what you're actually doing, you may want to rethink this overall design - this sort of thing is very fragile to changes in parent classes, and depending on where these custom subclasses come from, it would be very easy to end up running malicious code.
The OP code gets the name from the JarEntry, strips the ".class" and calls ClassLoader.loadClass.
The "name" of a ZIP entry is the relative path separated by '/' characters (all platforms). However, loadClass requires a binary name separated by '.' characters. Therefore, replace all '/' characters in the name with '.'.
.replace('/', '.')
Related
I'm trying to refactor some legacy code using java.io.File to use java.nio.file.Path instead.
I'm bitten by the fact that Path has better support for absolute filepaths, because a lot of the values I receive have a leading slash (/) while they are supposed to represent relative paths. String concatenation is easier that way but I'm trying to move away from String/File to represent filepaths.
The old behaviour was that new File(parent, child) returned a new File representing
a child file/directory under the parent directory, regardless of whether child started with /.
The new behaviour was that parent.resolve(child) returned a new Path representing either
a child file/directory under the parent directory
child as the root (if it started with /)
I think the new way can allow for cleaner code, but when refactoring a legacy application it can introduce subtle bugs.
What is the best/cleanest way to get back the old (File) behaviour while using Path?
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
class Main {
public static void main(String[] args) {
file();
path();
}
public static void file(){
File root = new File("/root");
File relative = new File(root, "relative");
File absolute = new File(root, "/absolute");
System.out.println("File:");
System.out.println(relative.getAbsolutePath()); // prints "/root/relative"
System.out.println(absolute.getAbsolutePath()); // prints "/root/absolute"
System.out.println();
}
public static void path(){
Path root = Paths.get("/root");
Path relative = root.resolve("relative");
Path absolute = root.resolve("/absolute");
System.out.println("Path:");
System.out.println(relative.toAbsolutePath()); // prints "/root/relative"
System.out.println(absolute.toAbsolutePath()); // prints "/absolute" but should print "/root/absolute"
System.out.println();
}
}
Basically what I want is a method that takes a parent Path, and a child String that returns me the parent+child Path regardless of whether the child had a leading /.
Something like this, but without the String manipulations that depend on me knowing that the configuration will use / (and not \):
private static Path resolveSafely(Path parent, String child) {
child = child.startsWith("/")
? child.substring(1)
: child;
parent.resolve(child);
}
The best way I could find is this:
Path root = Paths.get("/root");
Path relative = root.resolve("relative");
Path absolute = Paths.get(root.toString(), "/absolute");
System.out.println("Path:");
System.out.println(relative.toAbsolutePath()); // prints "/root/relative"
System.out.println(absolute.toAbsolutePath()); // prints "/root/absolute"
System.out.println();
Hopefully that's all you need.
Edit: Since Java 11, Path.of() is available and is the recommended way of obtaining Path objects instead of Paths.get(). Check javadoc which also states that Paths class may be deprecated in a future release.
My task is to show a tree of all directories/files of a PC drive,
I have a class DirectoryNode that extends DefaultMutableTreeNode with File field directoryPath. I build nodes recursively:
public void buildDirectoryTree(){
if(!directoryPath.isDirectory()){
return;
}
for(File f : directoryPath.listFiles()){
if(f.isHidden() || !f.exists()) continue;
DirectoryNode newChild = new DirectoryNode(f);
add(newChild);
newChild.buildDirectoryTree();
}
}
It works fine for concrete directories, but when I try to use it for whole drive, or some large directories, JTree with this node does not show up at all
I think it encounters a problem with specific directories. I've add exists and is Hidden checks to skip this problem roots, but it didn't help.
In addition, exists, isHidden and isDirectory return false for some of my valid directories directories (I am using Windows 10).
File.listFiles() is one of those ancient methods that violates Java's convention/good practice to never return null from a method that returns an array. So you have to check for null.
From the docs:
An array of abstract pathnames denoting the files and directories in
the directory denoted by this abstract pathname. The array will be
empty if the directory is empty. Returns null if this abstract
pathname does not denote a directory, or if an I/O error occurs.
I have changed your code to make it a little safer. If it's called from the EDT, you might want to add some log message or the like instead of throwing the exception into nirvana.
public void buildDirectoryTree() throws IOException {
if (!directoryPath.isDirectory()) {
return;
}
final File[] files = directoryPath.listFiles();
if (files != null) {
for (File f : files) {
if (f.isHidden() || !f.exists()) continue;
DirectoryNode newChild = new DirectoryNode(f);
add(newChild);
newChild.buildDirectoryTree();
}
} else {
throw new IOException("Failed to list files for " + directoryPath);
}
}
As others have pointed out, there are more modern APIs and they have been introduced for good reasons. I recommend to read up on NIO2 and the Path APIs for better solutions.
I need to programmatically find out which JRE classes can be referenced in a compilation unit without being imported (for static code analysis). We can disregard package-local classes. According to the JLS, classes from the package java.lang are implicitly imported. The output should be a list of binary class names. The solution should work with plain Java 5 and up (no Guava, Reflections, etc.), and be vendor agnostic.
Any reliable Java-based solution is welcome.
Here are some notes on what I've tried so far:
At first glance, it seems that the question boils down to "How to load all classes from a package?", which is of course practically impossible, although several workarounds exist (e.g. this and this, plus the blog posts linked there). But my case is much simpler, because the multiple classloaders issue does not exist. java.lang stuff can always be loaded by the system/bootstrap classloader, and you cannot create your own classes in that package. Problem is, the system classloader does not divulge its class path, which the linked appoaches rely on.
So far, I haven't managed to get access to the system classloader's class path, because on the HotSpot VM I'm using, Object.class.getClassLoader() returns null, and Thread.currentThread().getContextClassLoader() can load java.lang.Object by delegation, but does not itself include the classpath. So solutions like this one don't work for me. Also, the list of guaranteed system properties does not include properties with this kind of classpath info (such as sun.boot.class.path).
It would be nice if I didn't have to assume that there is an rt.jar at all, and rather scan the list of resources used by the system classloader. Such an approach would be safer with respect to vendor specific JRE implementations.
Compiled classes appear to contain readable java/lang text. So I wrote a little bit of code to see if these imports can be extracted. It's a hack, so not reliable, but assuming you can extract/list all classes in a jar-file, this could be a starting point.
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
public class Q21102294 {
public static final String EXTERNAL_JAR = "resources/appboot-1.1.1.jar";
public static final String SAMPLE_CLASS_NAME = "com/descartes/appboot/AppBoot.class";
public static HashSet<String> usedLangClasses = new HashSet<String>();
public static void main(String[] args) {
try {
Path f = Paths.get(EXTERNAL_JAR);
if (!Files.exists(f)) {
throw new RuntimeException("Could not find file " + f);
}
URLClassLoader loader = new URLClassLoader(new URL[] { f.toUri().toURL() }, null);
findLangClasses(loader, SAMPLE_CLASS_NAME);
ArrayList<String> sortedClasses = new ArrayList<String>();
sortedClasses.addAll(usedLangClasses);
Collections.sort(sortedClasses);
System.out.println("Loaded classes: ");
for (String s : sortedClasses) {
System.out.println(s);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void findLangClasses(URLClassLoader loader, String classResource) throws Exception {
URL curl = loader.getResource(classResource);
if (curl != null) {
System.out.println("Got class as resource.");
} else {
throw new RuntimeException("Can't open resource.");
}
ByteArrayOutputStream bout = new ByteArrayOutputStream();
InputStream in = curl.openStream();
try {
byte[] buf = new byte[8192];
int l = 0;
while ((l = in.read(buf)) > -1) {
bout.write(buf, 0, l);
}
} finally {
in.close();
}
String ctext = new String(bout.toByteArray(), StandardCharsets.UTF_8);
int offSet = -1;
while ((offSet = ctext.indexOf("java/lang/", offSet)) > -1) {
int beginIndex = offSet;
offSet += "java/lang/".length();
char cnext = ctext.charAt(offSet);
while (cnext != ';' && (cnext == '/' || Character.isAlphabetic(cnext))) {
offSet += 1;
cnext = ctext.charAt(offSet);
}
String langClass = ctext.substring(beginIndex, offSet);
//System.out.println("adding class " + langClass);
usedLangClasses.add(langClass);
}
}
}
Gives the following output:
Got class as resource.
Loaded classes:
java/lang/Class
java/lang/ClassLoader
java/lang/Exception
java/lang/Object
java/lang/RuntimeException
java/lang/String
java/lang/StringBuilder
java/lang/System
java/lang/Thread
java/lang/Throwable
java/lang/reflect/Method
Source code of the used compiled class is available here.
OK, I misread the question. Checking the JLS, all I see is:
"Every compilation unit implicitly imports every public type name declared in the predefined package java.lang, as if the declaration import java.lang.*; appeared at the beginning of each compilation unit immediately after any package statement. As a result, the names of all those types are available as simple names in every compilation unit."
(http://docs.oracle.com/javase/specs/jls/se7/html/jls-7.html)
If you want to know which types that includes, it's going to vary from version to version of Java...
I would like to get a list of classes that are available at runtime and that match a simple name.
For example:
public List<String> getFQNs(String simpleName) {
...
}
// Would return ["java.awt.List","java.util.List"]
List<String> fqns = getFQNs("List")
Is there a library that would do this efficiently, or do I have to manually go through all classes in each classloader? What would be the correct way of doing that?
Thanks!
UPDATE
One responder asked me why I wanted to do this. Essentially, I want to implement a feature that is similar to "organize imports/auto import", but available at runtime. I don't mind if the solution is relatively slow (especially if I can then build a cache so subsequent queries become faster) and if it is a best-effort only. For example, I don't mind if I do not get dynamically generated classes.
UPDATE 2
I had to devise my own solution (see below): it uses some hints provided by the other responders, but I came to realize that it needs to be extensible to handle various environments. It is not possible to automatically traverse all classloaders at runtime so you have to rely on general and domain-specific strategies to get a useful list of classes.
I mixed the the answers from #Grodriguez and #bemace and added my own strategy to come up with a best-effort solution. This solution imitates at runtime the auto-import feature available at compile time.
The full code of my solution is here. Given a simple name, the main steps are:
Get a list of packages accessible from the current classloader.
For each package, try to load the fully qualified name obtained from package + simple name.
Step 2 is easy:
public List<String> getFQNs(String simpleName) {
if (this.packages == null) {
this.packages = getPackages();
}
List<String> fqns = new ArrayList<String>();
for (String aPackage : packages) {
try {
String fqn = aPackage + "." + simpleName;
Class.forName(fqn);
fqns.add(fqn);
} catch (Exception e) {
// Ignore
}
}
return fqns;
}
Step 1 is harder and is dependent on your application/environment so I implemented various strategies to get different lists of packages.
Current Classloader (may be useful to detect dynamically generated classes)
public Collection<String> getPackages() {
Set<String> packages = new HashSet<String>();
for (Package aPackage : Package.getPackages()) {
packages.add(aPackage.getName());
}
return packages;
}
Classpath (good enough for applications that are entirely loaded from the classpath. Not good for complex applications like Eclipse)
public Collection<String> getPackages() {
String classpath = System.getProperty("java.class.path");
return getPackageFromClassPath(classpath);
}
public static Set<String> getPackageFromClassPath(String classpath) {
Set<String> packages = new HashSet<String>();
String[] paths = classpath.split(File.pathSeparator);
for (String path : paths) {
if (path.trim().length() == 0) {
continue;
} else {
File file = new File(path);
if (file.exists()) {
String childPath = file.getAbsolutePath();
if (childPath.endsWith(".jar")) {
packages.addAll(ClasspathPackageProvider
.readZipFile(childPath));
} else {
packages.addAll(ClasspathPackageProvider
.readDirectory(childPath));
}
}
}
}
return packages;
}
Bootstrap classpath (e.g., java.lang)
public Collection<String> getPackages() {
// Even IBM JDKs seem to use this property...
String classpath = System.getProperty("sun.boot.class.path");
return ClasspathPackageProvider.getPackageFromClassPath(classpath);
}
Eclipse bundles (domain-specific package provider)
// Don't forget to add "Eclipse-BuddyPolicy: global" to MANIFEST.MF
public Collection<String> getPackages() {
Set<String> packages = new HashSet<String>();
BundleContext context = Activator.getDefault().getBundle()
.getBundleContext();
Bundle[] bundles = context.getBundles();
PackageAdmin pAdmin = getPackageAdmin(context);
for (Bundle bundle : bundles) {
ExportedPackage[] ePackages = pAdmin.getExportedPackages(bundle);
if (ePackages != null) {
for (ExportedPackage ePackage : ePackages) {
packages.add(ePackage.getName());
}
}
}
return packages;
}
public PackageAdmin getPackageAdmin(BundleContext context) {
ServiceTracker bundleTracker = null;
bundleTracker = new ServiceTracker(context,
PackageAdmin.class.getName(), null);
bundleTracker.open();
return (PackageAdmin) bundleTracker.getService();
}
Examples of queries and answers in my Eclipse environment:
File: [java.io.File, org.eclipse.core.internal.resources.File]
List: [java.awt.List, org.eclipse.swt.widgets.List, com.sun.xml.internal.bind.v2.schemagen.xmlschema.List, java.util.List, org.hibernate.mapping.List]
IResource: [org.eclipse.core.resources.IResource]
You probably cannot do this at all. There is no way for the JVM to know whether a class List in an arbitrarily named package a.b.c.d is available without attempting to load a.b.c.d.List first. You would need to test for all possible package names.
Without loading them all you could get the classpath property
String class_path = System.getProperty("java.class.path");
And then you could create a function to search the filesystem for classes in those locations. And you'd have to code it to also search inside jar files, and some of the classes may not actually be available due to incompatibilities that would only be revealed when you load them. But if you just want a best-guess of what's available, this might be viable. Maybe you should tell us why you want to do this so you can get some alternative suggestions?
Edit:
Ok, sounds like you should check out this thread and the ones linked in it: How do I read all classes from a Java package in the classpath?
In particular it appears the Spring framework does something similar, maybe you can look at that code: http://static.springsource.org/spring/docs/2.0.x/api/org/springframework/core/io/support/PathMatchingResourcePatternResolver.html
I am running an executable jar and wish to find a list of classes WITHIN the jar so that I can decide at run-time which to run. It's possible that I don't know the name of the jar file so cannot unzip it
You can not enumerate classes from a package of jar using Reflection API. This is also made clear in the related questions how-can-i-enumerate-all-classes-in-a-package and
can-i-list-the-resources-in-a-given-package. I once wrote a tool that lists all classes found in a certain classpath. It's too long to paste here, but here is the general approach:
find the used classpath. This is shown nicely by eirikma in another answer.
add other places where the ClassLoader might search for classes, e.g. bootclasspath, endorsed lib in JRE etc. (If you just have a simple app, then 1 + 2 are easy, just take the class path from property.)
readAllFromSystemClassPath("sun.boot.class.path");
readAllFromSystemClassPath("java.endorsed.dirs");
readAllFromSystemClassPath("java.ext.dirs");
readAllFromSystemClassPath("java.class.path");
Scan the classpath and split folders from JARs.
StringTokenizer pathTokenizer = new StringTokenizer(pPath, File.pathSeparator);
Scan the folders with File.listFiles and open the JARs with ZipFile.entries. Pay attention to inner classes and package access classes, you propably do not want them.
isInner = (pClassName.indexOf('$') > -1);
Convert the file name or path in the JAR to a proper classname (/ -> .)
final int i = fullName.lastIndexOf(File.separatorChar);
path = fullName.substring(0, i).replace(File.separatorChar, '.');
name = fullName.substring(i + 1);
Now you can use Reflection to load that class and have a look into it. If you just want to know stuff of about the class you can load it without resolving, or use a byte code engineering library like BCEL to open the class without loading it into the JVM.
ClassLoader.getSystemClassLoader().loadClass(name).getModifiers() & Modifier.PUBLIC
I am not sure if there is a way to list all classes visible to the current classloader.
Lacking that, you could
a) try to find out the name of the jar file from the classpath, and then look at its contents.
or
b) supposing that you have a candidate list of classes you are looking for, try each of them with Class.forName().
you can use a simple program to get a list of all the class files from jar and dump it in a property file on runtime and then in your program you can load req. class as and when req.; without using reflections.
You can get the actual classpath from the classloader. this must include the jar file, otherwise the program wouldn't run. Look throug the classpath URLs to find a URL that ends with ".jar" and contains something that is never changing in the name of you jar file (preferably after the last "/"). After that you open it as a regular jar (or zip) file and read the contents.
There are several methods available for obtaining the classpath. None of them works in every context and with every setup, so you must try them one by one until you find one that works in all the situations you need it to work. Also, sometimes you might need to tweak the runtime context, like (often needed) substituting maven surefire-plugin's classloading mechanism to one of optional (non-default) ones.
Obtaining the classpath 1: from system property:
static String[] getClasspathFromProperty() {
return System.getProperty("java.class.path").split(File.pathSeparator);
}
Obtaining the classpath 2: from classloader (with maven warning):
String[] getClasspathFromClassloader() {
URLClassLoader classLoader = (URLClassLoader) (getClass().getClassLoader());
URL[] classpath = classLoader.getURLs();
if (classpath.length == 1
&& classpath[0].toExternalForm().indexOf("surefirebooter") >= 0)
{
// todo: read classpath from manifest in surefireXXXX.jar
System.err.println("NO PROPER CLASSLOADER HERE!");
System.err.println(
"Run maven with -Dsurefire.useSystemClassLoader=false "
+"-Dsurefire.useManifestOnlyJar=false to enable proper classloaders");
return null;
}
String[] classpathLocations = new String[classpath.length];
for (int i = 0; i < classpath.length; i++) {
// you must repair the path strings: "\.\" => "/" etc.
classpathLocations[i] = cleanClasspathUrl(classpath[i].toExternalform());
}
return classpathLocations;
}
Obtaining the classpath 3: from current thread context: This is similar to method 2, except the first line of the method should read like this:
URLClassLoader classLoader
= (URLClassLoader)(Thread.currentThread().getContextClassLoader());
Good luck!
I would use a bytecode inspector library like ASM. This ClassVisitor can be used to look for the main method:
import org.objectweb.asm.*;
import org.objectweb.asm.commons.EmptyVisitor;
public class MainFinder extends ClassAdapter {
private String name;
private boolean isMainClass;
public MainFinder() {
super(new EmptyVisitor());
}
#Override
public void visit(int version, int access, String name,
String signature, String superName,
String[] interfaces) {
this.name = name;
super.visit(version, access, name, signature,
superName, interfaces);
}
#Override
public MethodVisitor visitMethod(int access, String name,
String desc, String signature, String[] exceptions) {
if ((access & Opcodes.ACC_PUBLIC) != 0
&& (access & Opcodes.ACC_STATIC) != 0
&& "main".equals(name)
&& "([Ljava/lang/String;)V".equals(desc)) {
isMainClass = true;
}
return super.visitMethod(access, name, desc, signature,
exceptions);
}
public String getName() {
return name;
}
public boolean isMainClass() {
return isMainClass;
}
}
Note that you might want to alter the code to confirm that classes are public, etc.
This sample app uses the above class on a command-line-specified JAR:
import java.io.*;
import java.util.Enumeration;
import java.util.jar.*;
import org.objectweb.asm.ClassReader;
public class FindMainMethods {
private static void walk(JarFile jar) throws IOException {
Enumeration<? extends JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
MainFinder visitor = new MainFinder();
JarEntry entry = entries.nextElement();
if (!entry.getName().endsWith(".class")) {
continue;
}
InputStream stream = jar.getInputStream(entry);
try {
ClassReader reader = new ClassReader(stream);
reader.accept(visitor, ClassReader.SKIP_CODE);
if (visitor.isMainClass()) {
System.out.println(visitor.getName());
}
} finally {
stream.close();
}
}
}
public static void main(String[] args) throws IOException {
JarFile jar = new JarFile(args[0]);
walk(jar);
}
}
You may also want to look at the "java.class.path" system property.
System.getProperty("java.class.path");
It is possible to use reflection to obtain similar results, but that approach may have some unfortunate side-effects - like causing static initializers to be run, or keeping unused classes in memory (they will probably stay loaded until their ClassLoader is garbage collected).