Determine the location of a Java package - java

I need to find the jar from a Java project that provides a certain logical Java package (e.g. com.example.functionality), but there are hundreds of them, and their names aren't particularly useful.
How to find out the mappings that are created between dirs/files/jars and packages/classes?

obj.getClass().getProtectionDomain().getCodeSource()
See: javadoc

You can do it in code:
Class myClass = Class.forName("com.example.functionality");
// eg. /com/example/functionality.class
String classfilePath = '/' + myClass.getName().replace(".", "/") + ".class";
URL location = myClass.getResource(classfilePath);
That URL will be the JAR file (or the class folder if it isn't in a jar).
Slightly hacky though - may not work for all classloaders.

For a one-off search, http://www.jarfinder.com/ is handy. It has in impressive index, which seems to know about everything in Maven Central as well as many other download sites around the web, and lets you search by class name to find which JARs contain that class.

Related

How to create a custom ClassLoader for nested JAR files

I am working with a Java library that has some nested JAR files in lib package.
I have 2 issues:
I cannot see referenced types in my IDE (I am using JetBrains IntelliJ)
Of course I get class not defined at runtime
I understand that I have to create and use a custom ClassLoader, will it solve both problems?
Is this the recommended way of achieving this result?
The JAR file is an Italian government provided library and I cannot modify it as it will be periodically updated as the regulation changes.
Yes, as far as I know, the standard ClassLoaders do not support nested JARs. Which is sad, since it would be a really nice idea, but Oracle just doesn't give a damn about it. Here is a 18-year old ticket:
https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4735639
If you are getting those JARs from somebody else, the best thing would be to contact the vendor and ask them for a delivery in standards-compatible format. From your answer I realize that this might be difficult to achieve, but I would still try to talk to them, because it's the right thing to do. I'm pretty sure that everybody else in your position has the same issue. According to industry standards, such situation would usually hint your vendor into using Maven repository for their deliverables.
If talking to your vendor fails, you can re-pack the JARs as you get them. I would recommend writing an automated script for that and making sure it gets run on each delivery. You can either put all .class files into one uber-JAR, or just move the nested JARs outside the enclosing JAR. Caveat 1: there can be more than one class with the same name, so you need to make sure to take the correct one. Caveat 2: if the JARs were signed, you will lose the signature (unless you sign them with your own).
Option 3: you can always implement your own ClassLoader to load the classes from anywhere, even from the kitchen sink.
This guy did exactly this: https://www.ibm.com/developerworks/library/j-onejar/index.html
The short summary is that such a ClassLoader has to perform recursive unzipping, which is a bit of a pain-in-the-ass because archives are essentially made for stream access and not for random access, but apart from that it's perfectly doable.
You can use his solution as a "wrapper loader" which will replace your main class.
As far as IntelliJ IDEA goes, I don't believe it supports this functionality out-of-the box. The best thing would be either to re-package JARs as described above and add them as separate classpath entries, or to search if anybody has written a plugin for nested JAR support.
I don't know what you want to do after load jars.
In my case, use jar dynamic loading for Servlet samples.
try{
final URLClassLoader loader = (URLClassLoader)ClassLoader.getSystemClassLoader();
final Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
new File(dir).listFiles(new FileFilter() {
#Override
public boolean accept(File jar) {
// load file if it is 'jar' type
if( jar.toString().toLowerCase().contains(".jar") ){
try {
method.invoke(loader, new Object[]{jar.toURI().toURL()});
XMLog.info_arr(logger, jar, " is loaded.");
JarInputStream jarFile = new JarInputStream(new FileInputStream(jar));
JarEntry jarEntry;
while (true) {
// load jar file
jarEntry = jarFile.getNextJarEntry();
if (jarEntry == null) {
break;
}
// load .class file in loaded jar file
if (jarEntry.getName().endsWith(".class")) {
Class loadedClass = Class.forName(jarEntry.getName().replaceAll("/", "\\.").replace(".class",""));
/*
* In my case, I load jar file for Servlet.
* If you want to use it for other case, then change below codes
*/
WebServlet annotaions = (WebServlet) loadedClass.getAnnotation(WebServlet.class);
// load annotation and mapping if it is Servlet
if (annotaions.urlPatterns().length > 0) {
ServletRegistration.Dynamic registration = servletContextEvent.getServletContext().addServlet(annotaions.urlPatterns().toString(), loadedClass);
registration.addMapping(annotaions.urlPatterns());
}
}
}
} catch (Exception e) {
System.err.println("Can't load classes in jar");
}
}
return false;
}
});
} catch(Exception e) {
throw new RuntimeException(e);
}
Interestingly I just solved a version of this problem for JesterJ, though I had the additional requirement of loading dependencies for the code in the jar file as well. JesterJ (as of this evening's commits!) runs from a fat jar and accepts an argument denoting a second fat jar containing the classes, dependencies and configuration for a document ingestion plan (the user's code that I need to run).
The way my solution works is I borrow the knowledge of how to load jars inside of jars from Uno-Jar (the library that produces the fat jar), and stuff my own classloader in above it to control the evaluation order of the class loaders.
The key bit from https://github.com/nsoft/jesterj/blob/jdk11/code/ingest/src/main/java/org/jesterj/ingest/Main.java looks like this:
JesterJLoader jesterJLoader;
File jarfile = new File(javaConfig);
URL planConfigJarURL;
try {
planConfigJarURL = jarfile.toURI().toURL();
} catch (MalformedURLException e) {
throw new RuntimeException(e); // boom
}
jesterJLoader = (JesterJLoader) ClassLoader.getSystemClassLoader();
ClassLoader loader;
if (isUnoJar) {
JarClassLoader jarClassLoader = new JarClassLoader(jesterJLoader, planConfigJarURL.toString());
jarClassLoader.load(null);
loader = jarClassLoader;
} else {
loader = new URLClassLoader(new URL[]{planConfigJarURL}, jesterJLoader);
}
jesterJLoader.addExtLoader(loader);
My JesterJLoader is here:
https://github.com/nsoft/jesterj/blob/jdk11/code/ingest/src/main/java/org/jesterj/ingest/utils/JesterJLoader.java
Though if you are happy to simply delegate up and rely on existing classes on the main class path (rather than loading additional dependencies from the sub-fat-jar like I'm doing) yours could be much simpler. I go to a lot of effort to allow it to check the sub-jar first rather than delegating up to the parent immediately, and then have to keep track of what's been sent to the sub-jar to avoid loops and subsequent StackOverflowError...
Also note that the line where I get the system class loader is going to NOT be what you want, I'm also monkeying with the system loader to work around impolite things that some of my dependencies are doing with class loading.
If you decide to try to check out Uno-Jar pls note that resource loading for this nested scenario may yet be wonky and things definitely won't work before https://github.com/nsoft/uno-jar/commit/cf5af42c447c22edb9bbc6bd08293f0c23db86c2
Also: recently committed thinly tested code warning :)
Disclosure: I maintain both JesterJ and Uno-Jar (a fork of One-JAR the library featured in the link supplied by jurez) and welcome any bug reports or comments or even contributions!

Check if plugin used "compile files" when it should've used "provided"

Little background for context:
The application I support allows third parties to develop plugins that can leverage some of our functionality. We hand them our "externalAPI.jar"; they put it in their project, implement some interfaces, and build their own APK. We find the would-be plugin by asking the package manager for all installed applications and see if each has a "pluginclass.xml" in the assets directory. If it has that XML file, we anticipate its contents being the canonical path of a class that implements our ExternalPluginVX interface, and using a new PathClassLoader(ApplicationInfo.sourceDir, this.getClass().getClassLoader()), we load the class, create a new instance, and start using it.
The problem:
Sometimes third parties will put
compile files ("./libs/externalAPI.jar")
in their gradle files instead of the correct syntax:
provided files ("./libs/externalAPI.jar")
The result of course being things don't work properly. Sometimes they almost work, but then have unpredictability in their behavior - usually involving vicious crashes. Notably, since their APK is well-formed in its own right, and the XML file is there, we'll see the plugin, load the target class successfully, instantiate it successfully, and things go haywire from there when they try and reference back to us.
The question:
Is there a way for my application to check at runtime if the other application compiled our API classes into their APK instead of using provided files like they should have?
A viable solution is to use a DexFile.
Since I already have the ApplicationInfo.sourceDir, I can just construct a DexFile and iterate through its contents.
//this variable's value assigned by iterating through context.getPackageManager().getInstalledApplications(0)
ApplicationInfo pkg;
String interfaceTheyShouldntHave = ExternalPluginVX.class.getCanonicalName(); //"com.project.external.ExternalPluginVX"
DexFile dexFile = new DexFile(pkg.sourceDir);
Enumeration<String> entries = dexFile.entries();
while(entries.hasMoreElements()){
String entry = entries.nextElement();
if(entry.equals(interfaceTheyShouldntHave)){
Toast.makeText(ctxt, "Plugin \"" + pluginName + "\" could not be loaded. Please use 'provided files' instead of 'compile files'", Toast.LENGTH_LONG).show();
return;
}
}

Parent parent directory in Java

Please explain me, why when i run this code, all is fine and i get parent directory of my classes:
URL dirUrl = PathsService.class.getResource("..");
and when I run this code:
URL dirUrl = PathsService.class.getResource("../..");
I get null in dirUrl.
I try like this:
URL dirUrl = PathsService.class.getResource("..//.."); all the same I have a null in dirUrl.
How can I get parent/parent/parent ... directory in Java?
NOTICE:
As others have already stated, basing any functionality on the retrieval of some parent directory is a very bad design idea (and one that is almost certain to fail too).
If you share more details about what you are trying to achieve (the big picture), someone could probably propose a better solution.
That said, you could try the following code:
import java.nio.file.*;
...
Path path = Paths.get(PathsService.class.getResource(".").toURI());
System.out.println(path.getParent()); // <-- Parent directory
System.out.println(path.getParent().getParent()); // <-- Parent of parent directory
Also note, that the above technic may work on your development environment, but may (and probably will) produce unexpected results when your application is "properly" deployed.
The result you are getting is perfectly right.
I think you have misunderstood the use of class.getResource.
Lets say you have package com.test and MyClass inside this package.
This MyClass.class.getResource(".") will give you the location of the file you are executing from. Means location of MyClass.
This MyClass.class.getResource("..") will go 1 level up in your package structure. So, this will return the location of directory test.
This MyClass.class.getResource("../..") will go further 1 level up. So, this will return the location of directory com.
Now, this MyClass.class.getResource("../../..") will attempt to go further 1 level up but since there is no package directory exists, this will return null.
So, class.getResource will not go out of the defined package structure and start accessing your computer directory. This is how this does not work. This only searches within your current class package structure.

Dumping java.lang.Class's originated from jar files

I am trying to find a way to collected all java.lang.Class's loaded from jar files but ignore the ones from the source code itself.
I have found java.lang.instrument.Instrumentation interface and thought it might serve the purpose, but it turned out not quite.... One of the available functions "getAllLoadedClasses" dump all java.lang.Class's out (which is good), but it not only dump onces got loaded from jar file and also loaded from the source file.
Is there a configuration that allows us to customize the setting so only the java.lang.Class's originated from jar files are dumped or there is better solution in the wild?
What I want to achieve in code representation will be something like below.
java.lang.Class[]
classesLoadedFromJars = getClassesLoadedFromJars();
for (java.lang.Class class : classesLoadedFromJars) {
// ..............
}
A word or two on the suggestion will be helpful!
Thanks in advance.
The class's classloader should be able to give you a clue as to where a certain class was loaded from.
ClassLoader loader = myClass.getClassLoader()
if (loader instanceof URLClassLoader) {
URLClassLoader uLoader = (URLClassLoader)loader;
URL cURL = uLoader.getResource(myClass.getName().replace('.', '/')+".class");
}
if cURL starts with jar:// , the class originated from a jar file

Recursively finding only directories with FileUtils.listFiles

I want to collect a list of all files under a directory, in particular including subdirectories. I like not doing things myself, so I'm using FileUtils.listFiles from Apache Commons IO. So I have something like:
import java.io.File;
import java.util.Collection;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
public class TestListFiles {
public static void main(String[] args) {
Collection<File> found = FileUtils.listFiles(new File("foo"),
TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE);
for (File f : found) {
System.out.println("Found file: " + f);
}
}
}
Problem is, this only appears to find normal files, not directories:
$ mkdir -p foo/bar/baz; touch foo/one_file
$ java -classpath commons-io-1.4.jar:. TestListFiles
Found file: foo/one_file
I'm already passing TrueFileFilter to both of the filters, so I can't think of anything more inclusive. I want it to list: "foo", "foo/one_file", "foo/bar", "foo/bar/baz" (in any order).
I would accept non-FileUtils solutions as well, but it seems silly to have to write my own BFS, or even to collect the set of parent directories from the list I do get. (That would miss empty subdirectories anyway.) This is on Linux, FWIW.
An old answer but this works for me:
FileUtils.listFilesAndDirs(new File(dir), TrueFileFilter.INSTANCE, DirectoryFileFilter.DIRECTORY);
shows both:
I use:
FileUtils.listFilesAndDirs(new File(dir), new NotFileFilter(TrueFileFilter.INSTANCE), DirectoryFileFilter.DIRECTORY)
Only shows directories and not files...
Have you tried simply:
File rootFolder = new File(...);
File[] folders = rootFolder.listFiles((FileFilter) FileFilterUtils.directoryFileFilter());
It seems to work for me.
You will need recursion, of course.
Hope it helps.
I avoid the Java IO libraries in most of my non-trivial applications, preferring Commons VFS instead. I believe a call to this method with the appropriate params will accomplish your goal, but I'll grant its a long way to go for the functionality.
Specifically, this code will do what you want:
FileObject[] files = fileObject.findFiles(new FileSelector() {
public boolean includeFile(FileSelectInfo fileInfo) {
return fileInfo.getFile().getType() == FileType.FOLDER; }
public boolean traverseDescendents(FileSelectInfo fileInfo) {
return true;
}
});
where fileObject is an instance of FileObject.
If you look at the source code and read between the lines in the JavaDoc, you will see that -- unfortunately -- this API is not designed to do what you want. It will return a list of files (not a list of files and directories) that match the provided arguments. In the source code -- look at the method innerListFiles -- you will see that directories are searched and not added to the result list.
I am not aware of any public API that will do what you want. Hopefully someone else will know of one. Most will probably be a DFS, not a BFS, which may or may not matter for your purposes. (So far, all Java code I've ever looked at that did a directory tree traversal did it via a depth-first search. Which doesn't mean that BFS's aren't out there, of course.)
If you really want a list of everything under a given directory, it's easy enough to roll your own. But I understand your wish to not reinvent the wheel.
Note: It's possible that Apache Commons Finder will support what you need, but this library is in The Commons Sandbox, which means it is more experimental at this stage. It may or may not be complete and it may or may not be maintained. It also may be heavyweight for what you are looking for.
An easier+complete Commons VFS solution:
FileSystemManager fsManager = VFS.getManager();
FileObject fileObject = fsManager.resolveFile( "yourFileNameHere" );
FileObject[] files = fileObject.findFiles( new FileTypeSelector( FileType.FOLDER ) )
It should work, based on their API.
Here is my own version of FileUtils, not as complete as Commons IO, it contains only what I need. Search for findFiles or you can use iterate to avoid creating huge lists(sometime/most of the time you just want to do something with those files so collecting them in a List it doesn't makes sense).

Categories

Resources