Specify Output Path for Dynamic Compilation
Asked Answered
C

3

12

My dynamic compilation in Java 6 is working perfectly. However, I would like to change the output path. I have tried tons of things (I'll spare you) to no avail. Anyway, here's the working code

String[] filesToCompile = { "testFiles/Something.java" };
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(filesToCompile);
CompilationTask task = compiler.getTask(null, fileManager, null,null, null, compilationUnits);
System.out.println("Good? " + task.call());

But the output goes to the source directory, which is not what I want.

I suspect that the answer may lie in the compiler.getTask but the API is not very explicit as to what some of the parameters might mean. Or perhaps something with the fileManager. I've tried

fileManager.setLocation(StandardLocation.locationFor("testFiles2"), null);

but again, guessing is probably not a good idea.

Thanks!

Edit: I've tried using options, too, like this (sorry if there's a more compact way):

    final List<String> optionsList = new ArrayList<String>();
    optionsList.add("-d what");
    Iterable<String> options = new Iterable<String>() {         
        public Iterator<String> iterator() {
            return optionsList.iterator();
        }
    };

and then passing the options to getTask, but error message is "Invalid Flag."

Causation answered 8/1, 2010 at 14:32 Comment(2)
+1 for making me aware that there is such a thing as dynamic compilation now!Logia
There always was, now it's built-in!Causation
I
8

Code in the first post would work, but the following error get's thrown:

java.lang.IllegalArgumentException: invalid flag: -d folder

This is because by passing "-d folder" makes the parser think it's parsing one option. The options must be separated like "-d", "folder".

Working example follows:

JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager sjfm = javaCompiler.getStandardFileManager(null, null, null); 

String[] options = new String[] { "-d", "output" };
File[] javaFiles = new File[] { new File("src/gima/apps/flip/TestClass.java") };

CompilationTask compilationTask = javaCompiler.getTask(null, null, null,
        Arrays.asList(options),
        null,
        sjfm.getJavaFileObjects(javaFiles)
);
compilationTask.call();
Interoceptor answered 23/9, 2011 at 16:34 Comment(2)
what about paths like "c:\\foo bar\\foo" or "c:/foo bar/foo" ? Can the whitespace or slashes be parsed correctly?Glenn
I don't know, but I'd guess that no escaping is necessary since the arguments are now being passed correctly (and because the nature of the original problem was exactly that of the parameters not being parsed separatedly). Try and report back?Interoceptor
G
14

I was facing this same problem today.

The answer ( using the regular getTask method instead of `run ) is to specify the output dir in the FileManager:

fileManager.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(outputDir));

And that's it!! :)

The documentation is a bit misleading, I mean, a sample could come very handy. But eventually it took me there.

EDIT

Here's a running sample:

    // write the test class
    File sourceFile   = new File("First.java");
    FileWriter writer = new FileWriter(sourceFile);

    writer.write(
            "package load.test;\n" +
            "public class First{}"
    );
    writer.close();

    // Get the java compiler for this platform
    JavaCompiler compiler    = ToolProvider.getSystemJavaCompiler();
    StandardJavaFileManager fileManager = compiler.getStandardFileManager(
            null,
            null,
            null);

    //--           H E R E    --// 
    // Specify where to put the genereted .class files
    fileManager.setLocation(StandardLocation.CLASS_OUTPUT, 
                            Arrays.asList(new File("/tmp")));
    // Compile the file
    compiler
        .getTask(null,
                fileManager,
                null,
                null,
                null,
                fileManager.getJavaFileObjectsFromFiles(Arrays.asList(sourceFile)))
        .call();
    fileManager.close();

    // delete the file
    sourceFile.deleteOnExit();
Guienne answered 8/9, 2010 at 4:53 Comment(2)
It's been a while since I've looked at this problem. So you're saying that your answer actually solves the entire problem?Causation
Works well - same as using "-d" in the getTask() options param, but cleaner.Ancestry
I
8

Code in the first post would work, but the following error get's thrown:

java.lang.IllegalArgumentException: invalid flag: -d folder

This is because by passing "-d folder" makes the parser think it's parsing one option. The options must be separated like "-d", "folder".

Working example follows:

JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager sjfm = javaCompiler.getStandardFileManager(null, null, null); 

String[] options = new String[] { "-d", "output" };
File[] javaFiles = new File[] { new File("src/gima/apps/flip/TestClass.java") };

CompilationTask compilationTask = javaCompiler.getTask(null, null, null,
        Arrays.asList(options),
        null,
        sjfm.getJavaFileObjects(javaFiles)
);
compilationTask.call();
Interoceptor answered 23/9, 2011 at 16:34 Comment(2)
what about paths like "c:\\foo bar\\foo" or "c:/foo bar/foo" ? Can the whitespace or slashes be parsed correctly?Glenn
I don't know, but I'd guess that no escaping is necessary since the arguments are now being passed correctly (and because the nature of the original problem was exactly that of the parameters not being parsed separatedly). Try and report back?Interoceptor
L
4

I have 0 experience with the Java 6 dynamic compiler tools. But nobody else has answered :)

The compilation task gets a FileManager object. If you use the standard one, then classes are generated in the source directory tree. What you could do is provide your own FileManager subclass with an overridden getFileForOutput method. The API description of getFileForOutput indicates that this will influence where your output (= class) files will go.

Update

How to hook up file managers

ForwardingJavaFileManager, ForwardingFileObject, and ForwardingJavaFileObject Subclassing is not available for overriding the behavior of a standard file manager as it is created by calling a method on a compiler, not by invoking a constructor. Instead forwarding (or delegation) should be used. These classes makes it easy to forward most calls to a given file manager or file object while allowing customizing behavior. For example, consider how to log all calls to JavaFileManager.flush():

   final Logger logger = ...;
   Iterable<? extends JavaFileObject> compilationUnits = ...;
   JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
   StandardJavaFileManager stdFileManager = compiler.getStandardFileManager(null, null, null);
   JavaFileManager fileManager = new ForwardingJavaFileManager(stdFileManager) {
       public void flush() {
           logger.entering(StandardJavaFileManager.class.getName(), "flush");
           super.flush();
           logger.exiting(StandardJavaFileManager.class.getName(), "flush");
       }
   };
   compiler.getTask(null, fileManager, null, null, null, compilationUnits).call();

Update 2

I read up on dynamic compilation and built my own app to do this. This code contains a bit too much ceremony (i.e. it could be simplified) but it works!

package yar;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;

public class DynamicCompiler {

   JavaCompiler compiler;

   public DynamicCompiler() {
      this.compiler = ToolProvider.getSystemJavaCompiler();
      if (this.compiler == null) {
         throw new NullPointerException("Cannot provide system compiler.");
      }
   }

   public void compile() {
      this.compiler.run(null, System.out, System.err, 
            "-d", "testFiles2", 
            "testFiles/Hello1.java", "testFiles/Hello2.java");
   }

   /**
    * @param args
    */
   public static void main(String[] args) {
      try {
         DynamicCompiler dc = new DynamicCompiler();
         dc.compile();
      } catch (Exception e) {
         System.err.println(e.getMessage());
      }
   }

}

I'm not sure how to get this code to work with a dynamically generated list of Java files; I'd probably just do compiler.run separately for each source file.

Logia answered 8/1, 2010 at 14:44 Comment(12)
This might be true, but unfortunately getJavaFileObjects is only on the StandardJavaFileManager.... I'll see what can be done anyway. If this were Ruby, your answer would be enough to monkey patch and be done :)Causation
In Java, the way to go is subclassing... ForwardingJavaFileManager implements StandardJavaFileManager and that's the one you'd use.Logia
In fact, it has a constructor you can wrap around the FileManager you'd get from compiler. You will want to make that constructor public in your derived class, of course.Logia
Thanks Carl, just tried that (wrapped the fileManager in a sub of ForwardingJavaFileManager<StandardJavaFileManager> and then passed that to the task). Problem is, getFileForOutput is NEVER called. But, ForwardingJavaFileManager does NOT implement StandardFileManager.Causation
Ack, I'm afraid you're right. Sorry about the wild goose chase!Logia
Added some info on how to do the magic of combining file managers. That's directly from Compiler API so hopefully should do the trick.Logia
Thanks for your persistence on this. I was using getFileObjects and not getJavaFileObjects, so the subclassing does work, but I still can't achieve the final result because I do not know how to create a Location object (replacing the one passed to getJavaFileObjects) that causes the classes to be outputted to a new location.Causation
Built my own demo app. It works and puts the generated classes where I ask it to. Please take a look at my updated answer!Logia
That's awesome. Package yar. I'm flattered. This will work for now, I'll shout back when I get it integrated to thank you again. Looks like the args can be a String array, so it should work out.Causation
Heh, I have an Eclipse project called StackOverflowJava and within its src folder I build classes named after the posters when the code fits into a single class; or packages named after the posters when I envision needing multiple classes. It's getting to be a bulging repository of all kinds of crazy little projects!Logia
I should know but don't how String arrays work in the context of variable argument lists. But I think you can make it work out. If not, there's always SO. Glad I was able to help!Logia
Here is your class, remixed just a tiny bit: compileyouidontevenknowyou.blogspot.com/2010/01/…. Add it to a recursive file lister (I'll post mine soon on my blog), and you're rolling.Causation

© 2022 - 2024 — McMap. All rights reserved.