How to set up classpath of JavaCompiler to multipl

2019-05-10 11:53发布

问题:

I am using JavaCompiler of javax.tools to compile some java code and I am trying to use wildcard in my classpath in order to include all the .jar files but I fail.

Here is my code:

    String classpath = "C:\tomcat6\webapps\myapp/WEB-INF/lib/javax.ws.rs-api-2.0-m10.jar;"
+ "C:\\tomcat6\\webapps\\myapp/WEB-INF/lib/javax.persistence-2.1.0.jar";

    Iterable<String> options = Arrays.asList("-d", classesBaseDir,
                    "-classpath", classpath);

    JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager,
                    diagnostics, options, null, file);
    boolean result = task.call();

The code above works just fine. But when I am trying to change the classpath to

String classpath = "C:\\tomcat6\\webapps\\myapp/WEB-INF/lib/*";

it fails with

    compiler.err.doesnt.exist|package javax.ws.rs does not exist
...
    symbol:   class GET
      location: class com.my.oasis.resources.TestClass
    09/04/2014 14:27:09:030 | COMPILER_DIAGNOSTIC | compileResource() - compiler.err.cant.resolve.location|cannot find symbol
    ...

I have also tried the following alterations

String classpath = "\"C:\\tomcat6\\webapps\\myapp/WEB-INF/lib/*\"";
String classpath = "'C:\\tomcat6\\webapps\\myapp/WEB-INF/lib/*'";

but none of them worked. Any ideas?

Thanks

Note: the reason why the path includes slashes and backslashes is because the my program identifies the environment in runtime and auto completes the path.

Edit: I am using tomcat 6 and java 1.7.0_21

回答1:

Wildcards: Since Java 1.6 wildcards are supported when using java/javaw/javac, more information: Windows/Solaris and Linux

example:

javac -cp "lib/*" Test.java

This uses all .jar files (not .class!) in the lib directory as classpath. This should not be confused with the *-expansion of your shell. -cp lib/* gets expanded to -cp lib/a.jar lib/b.jar which is not valid argument syntax. In order to avoid this you have to add quotation marks: -cp "lib/*"

The cause of your Problem: You are trying to call the Java compiler from source directly with its Java API. This source code does not contain the wildcard expansion. The JDK ships with a wrapper binary (javac,javadoc,javah,javap are all the same binary) which does some things and finally calls the compiler task. This wrapper also expands the wildcards in your classpath and therefore the compiler task doesn't have to do this anymore (and it doesn't). See at Compiler Readme section "build -> Notes -> The launcher". Launcher sourcecode.

Solution:

  1. A very poor solution would be to call javac through a Processbuilder. (This is not recommended since it is a complicated and error prone solution for a simple problem)
  2. Expand the wildcards yourself:

example code:

String classpath = buildClassPath("lib/", "test/", "lib/*");
System.out.println(classpath);
// output: lib/;test/;lib/a.jar;lib/b.jar;

This function takes all classpath entries and builds one classpath. Classpath entries with a wildcard in it will get expanded.

/**
 * This function builds a classpath from the passed Strings
 * 
 * @param paths classpath elements
 * @return returns the complete classpath with wildcards expanded
 */
private static String buildClassPath(String... paths) {
    StringBuilder sb = new StringBuilder();
    for (String path : paths) {
        if (path.endsWith("*")) {
            path = path.substring(0, path.length() - 1);
            File pathFile = new File(path);
            for (File file : pathFile.listFiles()) {
                if (file.isFile() && file.getName().endsWith(".jar")) {
                    sb.append(path);
                    sb.append(file.getName());
                    sb.append(System.getProperty("path.separator"));
                }
            }
        } else {
            sb.append(path);
            sb.append(System.getProperty("path.separator"));
        }
    }
    return sb.toString();
}


回答2:

Using backslashes or slashes makes no difference. But you obviously assume that the path is auto-globbed (like a normal command line would). This does not happen. So you run your compiler as you would with a command line arg of

-classpath 'C:\\tomcat6\\webapps\\myapp/WEB-INF/lib/*'

which is not what you want. You may look into the API docs for java.io.File.listFiles(FileFilter) or even java.nio.file.Files.walkFileTree(Path, FileVisitor) to gain a better understanding.

To expand a bit on that, when your shell sees C:\tomcat6\webapps\myapp/WEB-INF/lib/* it expands it into a space-separated list of whatever is in your WEB-INF/lib directory. This is called globbing, or since it's done automatically, auto-globbing.

Now Java doesn't do that, but you can build it yourself in a few lines of code:

StringBuilder sb = new StringBuilder();
String[] filenames = new File("C:\\tomcat6\\webapps\\myapp/WEB-INF/lib/").list();
for(int i = 0; i < filenames.length; i++) {
    if(i > 0) sb.append(File.pathSeparatorChar); // separate with ':' or ';' on Win
    sb.append(filenames[i]); // append the filename
}
Iterable<String> options = Arrays.asList("-d", classesBaseDir, "-cp", sb.toString())
...

Since Java1.7 you can also use Files.newDirectoryStream(Path) instead of list(File). With 1.8 you could even call join instead of joining manually.