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

Dan Rosenstark
  • 68,471
  • 58
  • 283
  • 421

3 Answers3

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();
OscarRyz
  • 196,001
  • 113
  • 385
  • 569
7

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();
Gima
  • 1,892
  • 19
  • 23
  • what about paths like "c:\\foo bar\\foo" or "c:/foo bar/foo" ? Can the whitespace or slashes be parsed correctly? – Gobliins Nov 05 '15 at 13:17
  • 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? – Gima Nov 07 '15 at 18:38
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.

Carl Smotricz
  • 66,391
  • 18
  • 125
  • 167
  • 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 :) – Dan Rosenstark Jan 08 '10 at 15:39
  • In Java, the way to go is subclassing... `ForwardingJavaFileManager` implements `StandardJavaFileManager` and that's the one you'd use. – Carl Smotricz Jan 08 '10 at 15:43
  • 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 Jan 08 '10 at 15:45
  • Thanks Carl, just tried that (wrapped the fileManager in a sub of ForwardingJavaFileManager and then passed that to the task). Problem is, getFileForOutput is NEVER called. But, ForwardingJavaFileManager does NOT implement StandardFileManager. – Dan Rosenstark Jan 08 '10 at 16:08
  • Ack, I'm afraid you're right. Sorry about the wild goose chase! – Carl Smotricz Jan 08 '10 at 16:21
  • 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 Jan 08 '10 at 16:26
  • 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. – Dan Rosenstark Jan 09 '10 at 11:25
  • 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 Jan 09 '10 at 12:41
  • 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. – Dan Rosenstark Jan 09 '10 at 13:29
  • 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 Jan 09 '10 at 14:31
  • 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 Jan 09 '10 at 14:32
  • 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. – Dan Rosenstark Jan 09 '10 at 15:25