Why Java cannot read same resource file when module-info is added? - java

There is a simple Java project with standard Maven folder structure.
src
main
java
mypackage
Main.java
resource
abc
cde.txt
Main.java (boilerplate omitted)
var path = "abc/cde.txt";
InputStream input = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
if (input == null) {
throw new IllegalStateException("Resource not found");
} else {
// read from input
}
This code works fine and read file from the absolute path
"%project_root%/target/classes/abc/cde.txt" (compiled code).
After adding file src/main/java/module-info.java the situation changes: the program cannot find the file and throws in branch (input == null).
How to read files from "resource" folder the old way and have both: java-module and resources in the resource folder? I would like to avoid adding a prefix "src/main/resources" everywhere.

You probably want this:
InputStream input = Main.class.getResourceAsStream("/abc/cde.txt");
When you add a module-info.java, your classes are considered a module by Java.
A module has encapsulation restrictions beyond what a plain old classpath has. To access resources in a module, other code must go through that module, which will check whether the calling code’s module has permission to read those resources.
ClassLoader.getResourceAsStream will only read resources from explicitly opened modules:
Additionally … this method will only find resources in packages of named modules when the package is opened unconditionally.
But Class.getResource and Class.getResourceAsStream only rely on the module to which the class belongs, and don’t have that additional restriction.
One should always use Class.getResource or Class.getResourceAsStream. The ClassLoader equivalents should be avoided.
There is an important difference between the Class methods and the ClassLoader methods: The Class methods treat the argument as relative to the class’s package, unless the argument starts with a slash (/).
Aside from the encapsulation restrictions, given a class named com.example.MyApplication, these two lines are equivalent:
MyApplication.class.getResource("data.txt")
MyApplication.class.getClassLoader().getResource("com/example/data.txt")
And these are equivalent:
MyApplication.class.getResource("/data.txt")
MyApplication.class.getClassLoader().getResource("data.txt")
Again, they are only equivalent in terms of the resource path; the modular encapsulation restrictions are not the same. Always use the Class.getResource* methods, and avoid the ClassLoader.getResource* methods.

Related

How to load a java class outside the classpath?

I have a program where I want the user to be able to choose a .java class file from the file system, and then have that class loaded into the program.
I'm using a JFileChooser to allow the user to select a file. Then, I tried converting that file to a URL, and using a URLClassLoader to load the class (as suggested by these answers).
The problem is that, when I want to use the loadClass() method, I don't know the "full class name" of the class (e.g. java.lang.String). So, I don't know how to make this method work. Is there a way to get this class name? Or is there another way to do this?
Here is a sample of my code:
// Open the file chooser
JFileChooser fileChooser = new JFileChooser();
fileChooser.showOpenDialog(null);
File obtainedFile = fileChooser.getSelectedFile();
// Create the class loader from the file
URL classPath = obtainedFile.toURI().toURL();
URLClassLoader loader = new URLClassLoader(new URL[] {classPath});
// Get the class from the loader
Class<?> theClassIWant = loader.loadClass("the file name"); // What do I put here??
Load a single class file is generally completely useless. Said class file isn't alone; it has more class files that are relevant. Even if you think 'nah, there is just one source file, do not worry about this', note that a single java file can easily generate multiple class files.
Thus, two options:
Don't load class files. Load jar files.
Use the usual mechanisms (META-INF/services or META-INF/MANIFEST.MF) to put some sort of class name in there so you know what to load. Then create a new classloader with the provided jar, load the manifest, figure out the main class, load that, and run it.
Attempt to determine the 'root' for the loaded class file and include that on the classpath.
This is quite difficult - the problem is, to 'load' a class file you need to tell the loader what the fully qualified name is of that class before it is loaded. But how do you know the fully qualified name? You can surmise the class name from the file (not quite always true, but usually), but the package is a more difficult issue.
You can open the class file yourself as a binary stream and write a basic class file format parser to get the fully qualified class name. Easy for an experienced java programmer. Quite tricky for someone new to java (which I gather you are, if you think this is a good idea).
You can also use existing tools to do this, such as bytebuddy or asm.
Finally, you can try a spaghetti-at-the-wall method: Keep travelling up the directory until it works. You know it isn't working if exceptions occur.
For example, to load C:\MyDir\Whatever\com\foo\MyApp.class, You first try creating a new classloader (see the API of URLClassLoader which is part of core java) using as root dir C:\MyDir\Whatever\com\foo, and then you ask it to load class MyApp.
If that works, great (but usually trying to load package-less classes is simply a non-starter, you're not supposed to do that, the CL API probably doesn't support it, intentionally, there is no fixing that).
If it doesn't, instead try C:\MyDir\Whatever\com, and load class foo.MyApp. If that doesn't work, try C:\MyDir\Whatever and load class com.foo.MyApp, and so on.
The considerable advantage is, if there is another class sitting right next to MyApp.class, and MyApp needs it, this will work fine.
You'll need to write a while loop (traversing the path structure using Paths.get and p.getParent()), catch the right exception, manipulate the path into the class name (using .replace and +), and, of course, create a class loader (URLClassLoader), load classes with it (invoke loadClass), and if you intend on running it, something like thatClass.getConstructor().newInstance() and then thatClass.getMethod("someMethod", String.class, /* all the other args here */).invoke(theInstanceYouJustMade, "param1", /*all other params */) to actually 'run' it, more to be found in the java.lang.reflect package.

Reflections returning Set with null elements

I'm using Reflections to find classes that have an specific annotation. My project structure is the following
One WAR package:
WEB-INF/classes/...packages.../ClassAnnoted1.class
One JAR package that is included by the war that has a class that executes this code:
Reflections reflections= new Reflections(ClasspathHelper.forWebInfClasses(servletContext))
Set set= reflections.getTypesAnnotatedWith(CustomAnnotation.class)
CustomAnnotation is also present on the JAR package.
the set size is correct (ie if I have 3 classes with the annotation in my WAR the jar, the set size comes back as 3), but all elements inside it are null instead of Class. I need to get the class and check the annotation parameters inside the class of the JAR.
Anyone got any idea of why this is happening?
EDIT:
Reflections reflections= new Reflections("com.my.customAnnotededClasses"); //package that my annoted class is in
Set set= reflections.getTypesAnnotatedWith(CustomAnnotation.class);
Also does not work, in this case the set length is zero instead of the number of classes with the annotation.
EDIT 2:
Ok, the real problem was that I was packaging my whole application as an EAR so I had the following:
EAR
----> WAR
----> JAR
The jar was included in the EAR lib folder and not on the WAR lib folder. So the jar classes couldn't see the war classes, once i made the WAR depend on the JAR directly like this:
EAR
----> WAR
---------> JAR
It started working. But the original question still stands, there might be situations where I want the Jar classes included in the EAR instead of the WAR (if i have multiple wars that need to use my jar for instance).
I guess I can't do it using the reflections library. So I did it by hand:
public static List<Class<?>> getClassesAnnotatedWith(Class annotation, ServletContext servletContext) {
List<Class<?>> webClasses, jarClasses;
webClasses= getClassesAnnotedWithFromClassLoader(annotation, servletContext.getClassLoader());
jarClasses= getClassesAnnotedWithFromClassLoader(annotation, Thread.currentThread().getContextClassLoader());
for (Class<?> jarClass : jarClasses) {
Class<?> elementToAdd= null;
for (Class<?> webClass : webClasses) {
if ( ! jarClass.getName().equals(webClass.getName())) {
elementToAdd= jarClass;
}
}
if(elementToAdd != null) {
webClasses.add(elementToAdd);
}
}
return webClasses;
}
private static List<Class<?>> getClassesAnnotedWithFromClassLoader(Class annotation, ClassLoader classLoader) {
List<Class<?>> classes= new ArrayList<Class<?>>();
Class<?> classLoaderClass= classLoader.getClass();
while (! classLoaderClass.getName().equals("java.lang.ClassLoader")) {
classLoaderClass= classLoaderClass.getSuperclass();
}
try {
Field fldClasses= classLoaderClass.getDeclaredField("classes");
fldClasses.setAccessible(true);
Vector<Class<?>> classesVector= (Vector<Class<?>>) fldClasses.get(classLoader);
for (Class c : classesVector) {
if (c.isAnnotationPresent(annotation)) {
classes.add(c);
}
}
} catch (Exception e) { }
return classes;
}
I get the ClassLoader from my WAR package through the ServletContext object. There is also a protection in case a class is defined in both the WAR and the JAR with the annotation and same name (you should probably check if the packages are the same too though).
Note that you should probably never use this code in your own projects (maybe only for debugging). It involves reflecting the ClassLoader class to make the "classes" property public. This property might not exists in Java 9 for example, so beware. This might also have some security problems if you are interacting modules written by third parties.
i had one a similar problem. are you sure, you included the annotation-classes into your classpath? if they are not loaded, they will somehow be found but not really returned and without any exception or anything
The Reflections library gave me various problems. Now I am using the reflection part of the Guava library: until now, no unexpected behavior has occurred.
In any case, I think that it is very rare that the source of the problem is the Java classloader.
Maybe try to load the class CustomAnnotation.class before to use it in the Reflections API.
Your code should work on conventional environments.
However, in different environments, such as osgi, you get:
1) urls with different protocol (bundle/vfs/...)
2) different class loader.
In the first case, you should a) add the relevant UrlType (see the DefaultUrlTypes in Vfs for examples), or b) use different method to get the urls (see other methods in ClasspathHelper and examine the returned URL list)
In the second case, you should a) pass the customClassLoader to Reflections constructor or ConfigurationBuilder in order resolving will happen, or b) query the store directly reflections.getStore().get(TypeAnnotationsScanner.class)
see also #8339845, JbossIntegration

How to get current class name including package name in Java?

I'm working on a project and one requirement is if the 2nd argument for the main method starts with “/” (for linux) it should consider it as an absolute path (not a problem), but if it doesn't start with “/”, it should get the current working path of the class and append to it the given argument.
I can get the class name in several ways: System.getProperty("java.class.path"), new File(".") and getCanonicalPath(), and so on...
The problem is, this only gives me the directory in which the packages are stored - i.e. if I have a class stored in ".../project/this/is/package/name", it would only give me "/project/" and ignores the package name where the actual .class files lives.
Any suggestions?
EDIT:
Here's the explanation, taken from the exercise description
sourcedir can be either absolute (starting with “/”) or relative to where we run the program from
sourcedir is a given argument for the main method. how can I find that path?
Use this.getClass().getCanonicalName() to get the full class name.
Note that a package / class name ("a.b.C") is different from the path of the .class files (a/b/C.class), and that using the package name / class name to derive a path is typically bad practice. Sets of class files / packages can be in multiple different class paths, which can be directories or jar files.
There is a class, Class, that can do this:
Class c = Class.forName("MyClass"); // if you want to specify a class
Class c = this.getClass(); // if you want to use the current class
System.out.println("Package: "+c.getPackage()+"\nClass: "+c.getSimpleName()+"\nFull Identifier: "+c.getName());
If c represented the class MyClass in the package mypackage, the above code would print:
Package: mypackage
Class: MyClass
Full Identifier: mypackage.MyClass
You can take this information and modify it for whatever you need, or go check the API for more information.
The fully-qualified name is opbtained as follows:
String fqn = YourClass.class.getName();
But you need to read a classpath resource. So use
InputStream in = YourClass.getResourceAsStream("resource.txt");
use this.getClass().getName() to get packageName.className and use this.getClass().getSimpleName() to get only class name

When will the "jar" command refuse to add a class to a .jar file?

I have 204 total classes (most of the classes are inner classes). For months, I have been building fine with SCons (SCons just calls the jar command).
For some reason, it stopped adding the last inner class for a particular class. For example, suppose I have the following classes:
class1
class2
class3
class4
class5
class6
...
class79
class80
Before this last change, SCons would jar everything fine. But NOW... it specifically does not add class80 to it's jar command. (I see an omission of the class80 in the jar command).
Is there an instance where the jar command just ignores certain classes?
----------- EDIT. I found the culprit. For some reason this inner class is not recognized my SCons!
vehicleFilter = new RowFilter<Object, Object>(){
public boolean include(Entry<? extends Object, ? extends Object> entry) {
{return false;}
};
You need to add JAVAVERSION='1.6' as an argument to your env.Java() call:
env.Java(target='classes', source='src', JAVAVERSION='1.6')
Without this, if you're compiling with a current javac, SCons won't determine the correct names for anonymous inner classes, so when those bad class file names get passed to jar, it will fail.
Rather than pass a whole list of class files to the Jar command, you can pass a directory. This avoids problems with SCons's java parser as SCons will scan the directory for files and jar up anything it finds.
Something like the following will compile files in "src" to the directory "classes", then create a jar from the contents of "classes":
env = Environment(tools=['javac', 'jar'])
env.Java(target='classes', source='src')
env.Jar(target='foo.jar', source=['classes', 'Manifest.txt'],
JARCHDIR='$SOURCE')
The manifest file "Manifest.txt" is in the root of your project here. The only requirement is that it begins with the text "Manifest-Version".
SCons may construct a command line by listing all classes to jar on it and that may get too long (either a platform limitation or a heuristic inside SCons).
You need to peek inside the SCons package to see what goes on.
Any particular reason you don't just use ant?

Why getResourceAsStream method is in Class class?

Why public InputStream getResourceAsStream(String name) is in Class class? It just give inputstream of file which is in jar file and there is no relation with Class class. so it can be static method and it can be in any class.
There is a relationship to the class:
The package of the class is taken into account - if you give call getResourceAsStream("baz.txt") on the class for foo.bar.SomeClass it will look for /foo/bar/baz.txt
The classloader is taken into account to find the resources in the first place - if it were a static method, how would it know which jar files (etc) to look in? There's more to life than the system classloader
It just give inputstream of file which is in jar file ...
Incorrect. Not all classloaders load resources from regular JAR file.
Some classloaders load from directories.
Some classloaders load from the network.
Some classloaders load from multiple sources.
All of this complexity is hidden from you when you use the ClassLoader API via Class in this case.
... and there is no relation with Class class.
Incorrect. See #Jon Skeet's answer. Note that calling Class.getResourceAsStream(String) gives a resource that belongs to the same security context as the class. This can be very important if there are multiple classloaders / security contexts in use.

Categories