Summary
ClassPath.getTopLevelClasses() returns empty list when the path of the classloader contains special characters.
How to reproduce this error
@Test
public void testMain() throws Exception {
ClassLoader loader = this.getClass().getClassLoader();
ClassPath p = ClassPath.from(loader);
ImmutableSet<ClassPath.ClassInfo> list = p.getTopLevelClasses("testpackage");
Assert.assertEquals(3, list.size());
}
@fluentfuture
Looks to be caused by This line
It's caused because URLs automatically encode file names, while the File class expects raw unencoded input.
Previously: https://github.com/google/guava/issues/1899
eamonnmcmanus suggested https://weblogs.java.net/blog/kohsuke/archive/2007/04/how_to_convert.html at that point. I'm not sure whether we had a reason for not doing it that way then.
I'm not sure whether incorrect URLs should be supported at all. I'd rather see an exception than no results. I believe that it is an URLClassLoader's responsibility to make sure it returns valid URLs. This is what should be supported in the first place. It's not anymore since 81b23cdc840018da4fc07ccfcd55c78391f44df0 as you already know.
Before:
https://github.com/google/guava/blob/10e33c03a6d68fe1404aa721a4a7e37c1e63c43b/guava/src/com/google/common/reflect/ClassPath.java#L291
entry.toURI() - yes, this could throw... in very rare circumstances.
After:
https://github.com/google/guava/blob/81b23cdc840018da4fc07ccfcd55c78391f44df0/guava/src/com/google/common/reflect/ClassPath.java#L289
entry.getFile() - if resource's path contains unsafe characters, you will most likely get them back... escaped.
Few days ago we switched from Guava 18 to 20. Our apps are mostly deployed into "C:\Program Files\Apache Software Foundation...". We have a lot of spaces in classpaths. Every time new聽File(entry.getFile()) gives something like "/C:/Apache%20Software%20Foundation/...", one .jar is ignored by the Scanner (!file.exists() is true).
It seems like 81b23cdc840018da4fc07ccfcd55c78391f44df0 fixed ClassPath behavior for some exotic case but ruined the rest. I understand that it's still @Beta, so no offence, shit happens.
I've created a small test for you. Guava 14 to 18 passes, Guava 19 and 20 fails.
https://github.com/perceptron8/guava-issue-2152-test~~
[Edit: unnecessary since 896c51a, deleted]
I'd be happy if you fix this bug just with the following. There is no backward compatibility already, so why not?
try {
file = new File(entry.toURI());
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
This would be used only for processing URLs returned by UrlClassLoader. At the time #1899 was submitted, getClassPathEntries() was used for resolving Manifest's Class-Path entries too. Now it's not AFAIK. Nothing wrong should happen. Not only spaces, but also percent signs could be supported again.
Some exhaustive unit tests for ClassPath would be really handy.
This bug just caused us to downgrade Guava from 19.0 to 18.0, so we would be delighted if this issue could be fixed.
/cc @cpovirk Any idea who the right owner should be?
Besides @fluentfuture, you could try @eamonnmcmanus and @lukesandberg.
@cpovirk The blog you mentioned at https://weblogs.java.net/blog/kohsuke/archive/2007/04/how_to_convert.html has a comment recommending, i.e., Paths.get (entry.toURI ()).toFile () to handle UNC paths on Windows. Any issues with this approach? (Since Guava is migrating to Java 8 anyway...)
Hopefully fixed with the commit mentioned above. Please let us know if you still see problems.
I'm running into this problem in v21.0. Reverting to v18.0 solved the problem. Could you confirm which version the commit mentioned above ended up in, or is expected to be in?
Thanks
Sorry, this will be in 22.0.
This problem still exists in java 9
I am experiencing this issue with Guava 23.0 and Eclipse 4.72. The following code is the simplest example that I could make. But it just returns an empty collection!
import com.google.common.reflect.*;
import java.io.*;
class Test {
public static void main(String[] args) {
try {
System.out.println(ClassPath.from(Test.class.getClassLoader()).getAllClasses());
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
}
https://stackoverflow.com/questions/49267460/how-do-i-use-classpathgetallclasses-in-guava
Try with 24.0, it works for me even on java 9
@UnAfraid Thank you, updating to version 24 worked!
Im having a similar issue. I have a springboot project. And this block of code:
ClassPath classPath = ClassPath.from(ClassRegistration.class.getClassLoader());
System.out.println(ClassRegistration.class.getPackage().getName());
System.out.println(classPath.getTopLevelClasses(ClassRegistration.class.getPackage().getName()).size());
System.out.println(classPath.getAllClasses().size());
Output is:
com.techx.htsbr.socket.message.receive
0
9247
I can run on intellij successfully. But when I package project to a jar file and run java -jar <filename.jar>, getTopLevelClasses method returns an emty list while getAllClasses method returns non-empty list. I'm using guava version 25.1-jre. same situation in version 19.0.
I'm having the same issue on Android Studio, where the classpath resources is empty hence no top level classes to return.
final List<Class> classes = new ArrayList<>();
try
{
final ClassPath classPath = ClassPath.from(Thread.currentThread().getContextClassLoader());
final ImmutableSet<ClassPath.ClassInfo> topLevel = classPath.getTopLevelClasses(PACKAGE_NAME);
final Collection<Object> classNames = Collections2.transform(topLevel, input -> input.getSimpleName());
while (classNames.iterator().hasNext())
{
final ClassPath.ClassInfo classInfo = (ClassPath.ClassInfo) classNames.iterator().next();
classes.add(classInfo.getClass());
}
}
catch (final IOException ex)
{
Log.e(TAG, "Unable to get classloader.", ex);
}
I can鈥檛 got it
@tuanhiep12, in your Spring Boot project you probably package your dependency JARs into the main JAR of your application (JAR in JAR) and use the Spring LaunchedURLClassLoader. The problem is that Guava doesn't understand JARs in JARs.
See https://github.com/spring-projects/spring-boot/issues/5338#issuecomment-192327258 for an alternative.
Does guava ClassPath class intend to support the spring boot jar layout?
Most helpful comment
Does guava ClassPath class intend to support the spring boot jar layout?