SimpleCompiler.java
/*
* Janino - An embedded Java[TM] compiler
*
* Copyright (c) 2001-2010 Arno Unkrig. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
* following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.codehaus.commons.compiler.jdk;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.tools.JavaCompiler;
import org.codehaus.commons.compiler.CompileException;
import org.codehaus.commons.compiler.Cookable;
import org.codehaus.commons.compiler.ErrorHandler;
import org.codehaus.commons.compiler.ISimpleCompiler;
import org.codehaus.commons.compiler.Location;
import org.codehaus.commons.compiler.WarningHandler;
import org.codehaus.commons.compiler.io.Readers;
import org.codehaus.commons.compiler.util.LineAndColumnTracker;
import org.codehaus.commons.compiler.util.reflect.ByteArrayClassLoader;
import org.codehaus.commons.compiler.util.resource.MapResourceCreator;
import org.codehaus.commons.compiler.util.resource.MapResourceFinder;
import org.codehaus.commons.compiler.util.resource.MultiResourceFinder;
import org.codehaus.commons.compiler.util.resource.Resource;
import org.codehaus.commons.compiler.util.resource.ResourceFinders;
import org.codehaus.commons.compiler.util.resource.StringResource;
import org.codehaus.commons.nullanalysis.NotNullByDefault;
import org.codehaus.commons.nullanalysis.Nullable;
/**
* {@code javax.tools}-based implementation of {@link ISimpleCompiler}.
*/
public
class SimpleCompiler extends Cookable implements ISimpleCompiler {
private ClassLoader parentClassLoader = Thread.currentThread().getContextClassLoader();
private final Compiler compiler;
/**
* Is {@code null} iff this {@link SimpleCompiler} is not yet cooked.
*/
@Nullable private Map<String, byte[]> bytecodes;
// See "addOffset(String)".
private final LineAndColumnTracker tracker = LineAndColumnTracker.create();
private final SortedSet<Location> offsets = new TreeSet<>(new Comparator<Location>() {
@Override @NotNullByDefault(false) public int
compare(Location l1, Location l2) {
return (
l1.getLineNumber() < l2.getLineNumber() ? -1 :
l1.getLineNumber() > l2.getLineNumber() ? 1 :
l1.getColumnNumber() - l2.getColumnNumber()
);
}
});
public
SimpleCompiler() { this.compiler = new Compiler(); }
/**
* Initializes with a <em>different</em>, {@code javax.tools.JavaCompiler}-compatible Java compiler.
*/
public
SimpleCompiler(JavaCompiler javaCompiler) { this.compiler = new Compiler(javaCompiler); }
@Override public void
setSourceVersion(int version) { this.compiler.setSourceVersion(version); }
@Override public void
setTargetVersion(int version) { this.compiler.setTargetVersion(version); }
@Override public Map<String /*className*/, byte[] /*bytecode*/>
getBytecodes() { return this.assertCooked(); }
private Map<String /*className*/, byte[] /*bytecode*/>
assertCooked() {
Map<String /*className*/, byte[] /*bytecode*/> result = this.bytecodes;
if (result == null) throw new IllegalStateException("Must only be called after \"cook()\"");
return result;
}
@Override public ClassLoader
getClassLoader() {
ClassLoader result = this.getClassLoaderCache;
if (result != null) return result;
final Map<String, byte[]> bytecode = this.getBytecodes();
// Create a ClassLoader that loads the generated classes.
result = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
@Override public ClassLoader
run() {
return new ByteArrayClassLoader(
bytecode, // classes
SimpleCompiler.this.parentClassLoader // parent
);
}
});
return (this.getClassLoaderCache = result);
}
@Nullable private ClassLoader getClassLoaderCache;
@Override public void
cook(@Nullable String fileName, Reader r) throws CompileException, IOException {
// Reset the "offsets" and the line-and-column-tracker; see "addOffset(String)".
this.tracker.reset();
this.offsets.clear();
r = Readers.trackLineAndColumn(r, this.tracker);
// Create one Java source file in memory, which will be compiled later.
String text = Readers.readAll(r);
Resource compilationUnit = new StringResource(fileName == null ? "simplecompiler" : fileName, text);
// The default classpath of JAVAC is "." - we don't want that.
this.compiler.setClassPath(new File[0]);
// Create and find class files in a HashMap.
Map<String, byte[]> bcs = (this.bytecodes = new HashMap<>());
this.compiler.setClassFileFinder(new MultiResourceFinder(
ResourceFinders.fromClassLoader(SimpleCompiler.this.parentClassLoader),
new MapResourceFinder(bcs)
));
this.compiler.setClassFileCreator(new MapResourceCreator(bcs));
this.compiler.compile(new Resource[] { compilationUnit }, this.offsets);
}
@Override public void
setDebuggingInformation(boolean debugSource, boolean debugLines, boolean debugVars) {
this.compiler.setDebugSource(debugSource);
this.compiler.setDebugLines(debugLines);
this.compiler.setDebugVars(debugVars);
}
@Override public void
setParentClassLoader(@Nullable ClassLoader parentClassLoader) {
this.parentClassLoader = (
parentClassLoader != null
? parentClassLoader
: Thread.currentThread().getContextClassLoader()
);
}
/**
* @deprecated Auxiliary classes never really worked... don't use them.
*/
@Deprecated public void
setParentClassLoader(@Nullable ClassLoader parentClassLoader, Class<?>[] auxiliaryClasses) {
this.setParentClassLoader(parentClassLoader);
}
@Override public void
setCompileErrorHandler(@Nullable ErrorHandler compileErrorHandler) {
this.compiler.setCompileErrorHandler(compileErrorHandler);
}
@Override public void
setWarningHandler(@Nullable WarningHandler warningHandler) { this.compiler.setWarningHandler(warningHandler); }
/**
* Derived classes call this method to "reset" the current line and column number at the currently read input
* character, and also changes the "file name" (see {@link #cook(String, Reader)}).
*/
protected void
addOffset(@Nullable String fileName) {
LineAndColumnTracker t = this.tracker;
assert t != null;
this.offsets.add(new Location(fileName, t.getLineNumber(), t.getColumnNumber()));
}
}