views:

240

answers:

2

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."

+2  A: 

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.

Carl Smotricz
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 :)
Yar
In Java, the way to go is subclassing... `ForwardingJavaFileManager` implements `StandardJavaFileManager` and that's the one you'd use.
Carl Smotricz
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.
Carl Smotricz
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.
Yar
Ack, I'm afraid you're right. Sorry about the wild goose chase!
Carl Smotricz
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.
Carl Smotricz
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.
Yar
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!
Carl Smotricz
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.
Yar
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!
Carl Smotricz
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!
Carl Smotricz
Here is your class, remixed just a tiny bit: http://compileyouidontevenknowyou.blogspot.com/2010/01/dynamic-java-compilation-to-output.html. Add it to a recursive file lister (I'll post mine soon on my blog), and you're rolling.
Yar
+1  A: 

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();
OscarRyz
It's been a while since I've looked at this problem. So you're saying that your answer actually solves the entire problem?
Yar
Yeap!!!, pretty much. I'll update the answer with a sample
OscarRyz