package ij.plugin;
import ij.*;
import ij.gui.*;
import ij.io.*;
import ij.plugin.frame.Editor;
import ij.plugin.Macro_Runner;
import ij.plugin.filter.PlugInFilter;
import ij.plugin.filter.PlugInFilterRunner;
import java.awt.Font;
import java.lang.reflect.Method;
import java.io.*;
import java.util.*;
import javax.tools.*;
public class Compiler implements PlugIn, FilenameFilter {
private static final String info =
"Library JAR files (e.g., imagescience.jar) should\n"
+"be located in either plugins/jars or plugins/lib.\n \n"
+"The javac command line will be displayed in\n"
+"the Log window if ImageJ is in debug mode.";
private static final int TARGET14=0, TARGET15=1, TARGET16=2, TARGET17=3, TARGET18=4, TARGET19=5;
private static final String[] targets = {"1.4", "1.5", "1.6", "1.7", "1.8", "1.9"};
private static final String TARGET_KEY = "javac.target";
private static CompilerTool compilerTool;
private static String dir, name;
private static Editor errors;
private static boolean generateDebuggingInfo;
private static int target = (int)Prefs.get(TARGET_KEY, TARGET18);
private static boolean checkForUpdateDone;
public void run(String arg) {
if (arg.equals("edit"))
edit();
else if (arg.equals("options"))
showDialog();
else {
if (arg!=null && arg.length()>0 && !arg.endsWith(".java"))
IJ.error("Compiler", "File name must end with \".java\"");
else
compileAndRun(arg);
}
}
void edit() {
if (open("", "Open macro or plugin")) {
Editor ed = (Editor)IJ.runPlugIn("ij.plugin.frame.Editor", "");
if (ed!=null) ed.open(dir, name);
}
}
void compileAndRun(String path) {
if (!open(path, "Compile and Run Plugin..."))
return;
if (name.endsWith(".class")) {
runPlugin(name.substring(0, name.length()-1));
return;
}
if (!isJavac()) {
if (IJ.debugMode) IJ.log("Compiler: javac not found");
if (!checkForUpdateDone) {
checkForUpdate("/plugins/compiler/Compiler.jar", "1.48c");
checkForUpdateDone = true;
}
Object compiler = IJ.runPlugIn("Compiler", dir+name);
if (compiler==null) {
boolean ok = Macro_Runner.downloadJar("/plugins/compiler/Compiler.jar");
if (ok)
IJ.runPlugIn("Compiler", dir+name);
}
return;
}
if (compile(dir+name))
runPlugin(name);
}
private void checkForUpdate(String plugin, String currentVersion) {
int slashIndex = plugin.lastIndexOf("/");
if (slashIndex==-1 || !plugin.endsWith(".jar"))
return;
String className = plugin.substring(slashIndex+1, plugin.length()-4);
File f = new File(Prefs.getImageJDir()+"plugins"+File.separator+"jars"+File.separator+className+".jar");
if (!f.exists() || !f.canWrite()) {
if (IJ.debugMode) IJ.log("checkForUpdate: jar not found ("+plugin+")");
return;
}
String version = null;
try {
Class c = IJ.getClassLoader().loadClass("Compiler");
version = "0.00a";
Method m = c.getDeclaredMethod("getVersion", new Class[0]);
version = (String)m.invoke(null, new Object[0]);
}
catch (Exception e) {}
if (version==null) {
if (IJ.debugMode) IJ.log("checkForUpdate: class not found ("+className+")");
return;
}
if (version.compareTo(currentVersion)>=0) {
if (IJ.debugMode) IJ.log("checkForUpdate: up to date ("+className+" "+version+")");
return;
}
boolean ok = Macro_Runner.downloadJar(plugin);
if (IJ.debugMode) IJ.log("checkForUpdate: "+className+" "+version+" "+ok);
}
boolean isJavac() {
if (compilerTool==null)
compilerTool=CompilerTool.getDefault();
return compilerTool!=null;
}
boolean compile(String path) {
IJ.showStatus("compiling "+path);
String classpath = getClassPath(path);
Vector options = new Vector();
if (generateDebuggingInfo)
options.addElement("-g");
validateTarget();
options.addElement("-source");
options.addElement(targets[target]);
options.addElement("-target");
options.addElement(targets[target]);
options.addElement("-Xlint:unchecked");
options.addElement("-deprecation");
options.addElement("-classpath");
options.addElement(classpath);
Vector sources = new Vector();
sources.add(path);
if (IJ.debugMode) {
StringBuilder builder = new StringBuilder();
builder.append("javac");
for (int i=0; i< options.size(); i++){
builder.append(" ");
builder.append(options.get(i));
}
for (int i=0; i< sources.size(); i++){
builder.append(" ");
builder.append(sources.get(i));
}
IJ.log(builder.toString());
}
boolean errors = true;
String s = "not compiled";
if (compilerTool != null) {
final StringWriter outputWriter = new StringWriter();
errors = !compilerTool.compile(sources, options, outputWriter);
s = outputWriter.toString();
} else {
errors = true;
}
if (errors)
showErrors(s);
else
IJ.showStatus("done");
return !errors;
}
String getClassPath(String path) {
long start = System.currentTimeMillis();
StringBuffer sb = new StringBuffer();
sb.append(System.getProperty("java.class.path"));
File f = new File(path);
if (f!=null) sb.append(File.pathSeparator + f.getParent());
String pluginsDir = Menus.getPlugInsPath();
if (pluginsDir!=null)
addJars(pluginsDir, sb);
return sb.toString();
}
void addJars(String path, StringBuffer sb) {
String[] list = null;
File f = new File(path);
if (f.exists() && f.isDirectory())
list = f.list();
if (list==null)
return;
boolean isJarsFolder = path.endsWith("jars")|| path.endsWith("lib");
path = IJ.addSeparator(path);
for (int i=0; i<list.length; i++) {
File f2 = new File(path+list[i]);
if (f2.isDirectory())
addJars(path+list[i], sb);
else if (list[i].endsWith(".jar")&&(!list[i].contains("_")||isJarsFolder)) {
sb.append(File.pathSeparator+path+list[i]);
}
}
}
void showErrors(String s) {
if (errors==null || !errors.isVisible()) {
errors = (Editor)IJ.runPlugIn("ij.plugin.frame.Editor", "");
errors.setFont(new Font("Monospaced", Font.PLAIN, errors.getFontSize()));
}
if (errors!=null) {
ImageJ ij = IJ.getInstance();
if (ij!=null)
s = ij.getInfo()+"\n \n"+s;
errors.display("Errors", s);
}
IJ.showStatus("done (errors)");
}
boolean open(String path, String msg) {
boolean okay;
String fileName, directory;
if (path.equals("")) {
if (dir==null) dir = IJ.getDirectory("plugins");
OpenDialog od = new OpenDialog(msg, dir, name);
directory = od.getDirectory();
fileName = od.getFileName();
okay = fileName!=null;
String lcName = okay?fileName.toLowerCase(Locale.US):null;
if (okay) {
if (msg.startsWith("Compile")) {
if (!(lcName.endsWith(".java")||lcName.endsWith(".class"))) {
IJ.error("File name must end with \".java\" or \".class\".");
okay = false;
}
} else if (!(lcName.endsWith(".java")||lcName.endsWith(".txt")||lcName.endsWith(".ijm")||lcName.endsWith(".js")||lcName.endsWith(".py")||lcName.endsWith(".bsh"))) {
IJ.error("File name must end with \".java\", \".txt\", \".ijm\", \".js\" or \".py\"");
okay = false;
}
}
} else {
int i = path.lastIndexOf('/');
if (i==-1)
i = path.lastIndexOf('\\');
if (i>0) {
directory = path.substring(0, i+1);
fileName = path.substring(i+1);
} else {
directory = "";
fileName = path;
}
okay = true;
}
if (okay) {
name = fileName;
dir = directory;
Editor.setDefaultDirectory(dir);
}
return okay;
}
public boolean accept(File dir, String name) {
return name.endsWith(".java")||name.endsWith(".macro")||name.endsWith(".txt");
}
void runPlugin(String name) {
name = name.substring(0,name.length()-5); new PlugInExecuter(name);
}
public void showDialog() {
validateTarget();
GenericDialog gd = new GenericDialog("Compile and Run");
gd.addChoice("Target: ", targets, targets[target]);
gd.setInsets(15,5,0);
gd.addCheckbox("Generate debugging info (javac -g)", generateDebuggingInfo);
gd.addHelp(IJ.URL+"/docs/menus/edit.html#compiler");
Font font = IJ.font10;
gd.addMessage(info, font);
gd.showDialog();
if (gd.wasCanceled()) return;
target = gd.getNextChoiceIndex();
generateDebuggingInfo = gd.getNextBoolean();
validateTarget();
}
void validateTarget() {
if (target>TARGET19)
target = TARGET19;
if (target<TARGET16)
target = TARGET16;
if (target>TARGET16 && IJ.javaVersion()<7)
target = TARGET16;
if (target>TARGET17 && IJ.javaVersion()<8)
target = TARGET17;
if (target>TARGET18 && IJ.javaVersion()<9)
target = TARGET18;
Prefs.set(TARGET_KEY, target);
}
}
class PlugInExecuter implements Runnable {
private String plugin;
private Thread thread;
PlugInExecuter(String plugin) {
this.plugin = plugin;
thread = new Thread(this, plugin);
thread.setPriority(Math.max(thread.getPriority()-2, Thread.MIN_PRIORITY));
thread.start();
}
public void run() {
IJ.resetEscape();
IJ.runPlugIn("ij.plugin.ClassChecker", "");
runCompiledPlugin(plugin);
}
void runCompiledPlugin(String className) {
if (IJ.debugMode) IJ.log("Compiler: running \""+className+"\"");
IJ.resetClassLoader();
ClassLoader loader = IJ.getClassLoader();
Object thePlugIn = null;
try {
thePlugIn = (loader.loadClass(className)).newInstance();
if (thePlugIn instanceof PlugIn)
((PlugIn)thePlugIn).run("");
else if (thePlugIn instanceof PlugInFilter)
new PlugInFilterRunner(thePlugIn, className, "");
}
catch (ClassNotFoundException e) {
if (className.indexOf('_')!=-1)
IJ.error("Plugin or class not found: \"" + className + "\"\n(" + e+")");
}
catch (NoClassDefFoundError e) {
String err = e.getMessage();
if (IJ.debugMode) IJ.log("NoClassDefFoundError: "+err);
int index = err!=null?err.indexOf("wrong name: "):-1;
if (index>-1 && !className.contains(".")) {
String className2 = err.substring(index+12, err.length()-1);
className2 = className2.replace("/", ".");
if (className2.equals(className)) { int spaceIndex = err.indexOf(" ");
if (spaceIndex>-1) {
className2 = err.substring(0, spaceIndex);
className2 = className2.replace("/", ".");
}
}
if (className2.equals(className))
IJ.error("Plugin not found: "+className2);
else
runCompiledPlugin(className2);
return;
}
if (className.indexOf('_')!=-1)
IJ.error("Plugin or class not found: \"" + className + "\"\n(" + e+")");
}
catch (Exception e) {
IJ.handleException(e); }
}
}
abstract class CompilerTool {
public static class JavaxCompilerTool extends CompilerTool {
public boolean compile(List sources, List options, StringWriter log) {
if (IJ.debugMode) IJ.log("Compiler: using javax.tool.JavaCompiler");
try {
JavaCompiler javac = getJavac();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager fileManager = javac.getStandardFileManager(diagnostics, null, null);
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromStrings(sources);
JavaCompiler.CompilationTask task =javac.getTask(log, fileManager, null, options, null, compilationUnits);
fileManager.close();
return task.call();
} catch (Exception e) {
PrintWriter printer = new PrintWriter(log);
e.printStackTrace(printer);
printer.flush();
}
return false;
}
protected JavaCompiler getJavac() throws Exception {
JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
return javac;
}
}
public static class LegacyCompilerTool extends CompilerTool {
protected static Class javacC;
public boolean compile(List sources, List options, StringWriter log) {
if (IJ.debugMode) IJ.log("Compiler: using com.sun.tools.javac");
try {
final String[] args = new String[sources.size() + options.size()];
int argsIndex = 0;
for (int optionsIndex = 0; optionsIndex < options.size(); optionsIndex++)
args[argsIndex++] = (String) options.get(optionsIndex);
for (int sourcesIndex = 0; sourcesIndex < sources.size(); sourcesIndex++)
args[argsIndex++] = (String) sources.get(sourcesIndex);
PrintWriter printer = new PrintWriter(log);
Object javac = getJavac();
Class[] compileTypes = new Class[] { String[].class, PrintWriter.class };
Method compile = javacC.getMethod("compile", compileTypes);
Object result = compile.invoke(javac, new Object[] { args, printer });
printer.flush();
return Integer.valueOf(0).equals(result);
} catch (Exception e) {
e.printStackTrace(new PrintWriter(log));
}
return false;
}
protected Object getJavac() throws Exception {
if (javacC==null)
javacC = Class.forName("com.sun.tools.javac.Main");
return javacC.newInstance();
}
}
public static CompilerTool getDefault() {
CompilerTool javax = new JavaxCompilerTool();
if (javax.isSupported())
return javax;
CompilerTool legacy = new LegacyCompilerTool();
if (legacy.isSupported())
return legacy;
return null;
}
public abstract boolean compile(List sources, List options, StringWriter log);
protected abstract Object getJavac() throws Exception;
public boolean isSupported() {
try {
return null != getJavac();
} catch (Exception e) {
return false;
}
}
}