package org.checkerframework.eclipse.javac;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
import org.checkerframework.eclipse.CheckerPlugin;
import org.checkerframework.eclipse.actions.CheckerManager;
import org.checkerframework.eclipse.prefs.CheckerPreferences;
import org.checkerframework.eclipse.prefs.OptionLine;
import org.checkerframework.eclipse.util.Command;
import org.checkerframework.eclipse.util.PluginUtil;
import org.checkerframework.eclipse.util.Util;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.ui.console.MessageConsoleStream;
import org.osgi.framework.Bundle;
/**
* Runs the Checker Framework (i.e. javac with the appropriate bootclasspath, classpath, and option
* arguments).
*/
public class CommandlineJavacRunner implements CheckersRunner {
/** The location of the checkers.jar relative to the plugin directory */
public static final String CHECKERS_JAR_LOCATION = "lib/checker.jar";
public boolean verbose = false;
/** Names of source files to compile */
protected final List<String> fileNames;
/** checkers to run */
protected final String[] processors;
/** The classpath for this project */
protected final String classpath;
/** The bootclasspath for this project */
protected final String bootClasspath;
/** The output of running the Checker Framework Compiler */
protected String checkResult;
/** The location of checkers.jar */
protected File checkerJar;
/** Whether or not the checker in question has qualifiers that are not supplied by Aquals */
protected boolean hasQuals;
public CommandlineJavacRunner(
final String[] fileNames,
final String[] processors,
final String classpath,
final String bootClasspath,
final boolean hasQuals) {
this.fileNames = Arrays.asList(fileNames);
this.processors = processors;
//TODO: SEEMS THAT WHEN WE ARE USING @ ARGS THE CLASSPATH FROM THE JAR IS OVERRIDDEN - FIX THIS
this.checkerJar = locatePluginFile(CHECKERS_JAR_LOCATION);
this.classpath = checkerJar.getAbsolutePath() + File.pathSeparator + classpath;
this.bootClasspath = bootClasspath;
final IPreferenceStore prefs = CheckerPlugin.getDefault().getPreferenceStore();
this.verbose = prefs.getBoolean(CheckerPreferences.PREF_CHECKER_VERBOSE);
this.hasQuals = hasQuals;
}
/**
* Write the names of all files to be checked into a file-of-file-names(fofn) and then call the
* Checker Framework in "check-only" mode to check the files listed in the fofn
*/
public void run() {
try {
MessageConsoleStream out = CheckerPlugin.findConsole().newMessageStream();
final File srcFofn =
PluginUtil.writeTmpSrcFofn(
"CFPlugin-eclipse", true, PluginUtil.toFiles(fileNames));
final File classpathFofn =
PluginUtil.writeTmpCpFile("CFPlugin-eclipse", true, classpath);
final List<String> cmd =
createCommand(
srcFofn,
processors,
classpathFofn,
bootClasspath,
new PrintStream(out));
if (verbose) {
out.println(PluginUtil.join(" ", cmd));
out.println();
out.println("Classpath:\n " + classpath + "\n");
out.println("Source Files:\n " + PluginUtil.join("\n\t", fileNames));
}
final String[] cmdArr = cmd.toArray(new String[cmd.size()]);
checkResult = Command.exec(cmdArr);
if (verbose) {
printTrimmedOutput(out, checkResult);
out.println("\n*******************\n");
}
srcFofn.delete();
classpathFofn.delete();
} catch (IOException e) {
CheckerPlugin.logException(e, "Error calling javac");
}
}
/**
* @return The implicit annotations that should be used when running the Checker Framework
* compiler see -Djsr308_imports in the Checker Framework Manual
*/
private String implicitAnnotations(final String[] processors) {
return PluginUtil.join(File.pathSeparator, CheckerManager.getSelectedQuals(processors));
}
/**
* Create a list where each item in the list forms a part of the command for calling the Checker
* Framework compiler e.g. java -jar checker.jar -proc:only -classpath /this/projects/classpath
* -processor checkers.nullness.NullChecker @srcFofnPath
*
* @param srcFofn A file of file names that contains the paths of all files to compile
* @param processors Checkers to call on the given filenames
* @param classpathFofn A file of file names for the Eclipse project's classpath
* @param bootClassPath The Eclipse project's bootclasspath
* @return A list of strings that (when separated by spaces) will form a call to the Checker
* Framework compiler
*/
protected List<String> createCommand(
final File srcFofn,
final String[] processors,
final File classpathFofn,
final String bootClassPath,
PrintStream out) {
final Map<PluginUtil.CheckerProp, Object> props =
new HashMap<PluginUtil.CheckerProp, Object>();
final IPreferenceStore prefs = CheckerPlugin.getDefault().getPreferenceStore();
if (prefs.getBoolean(CheckerPreferences.PREF_CHECKER_IMPLICIT_IMPORTS) && this.hasQuals) {
props.put(PluginUtil.CheckerProp.IMPLICIT_IMPORTS, implicitAnnotations(processors));
}
final List<String> miscOptions = new ArrayList<String>();
addPreferenceOptions(miscOptions, prefs);
if (!miscOptions.isEmpty()) {
props.put(PluginUtil.CheckerProp.MISC_COMPILER, miscOptions);
}
props.put(PluginUtil.CheckerProp.A_DETAILED_MSG, true);
addProcessorOptions(props, prefs);
final String procsStr = PluginUtil.join(",", processors);
final String jdkPath = prefs.getString(CheckerPreferences.PREF_CHECKER_JDK_PATH);
return PluginUtil.getCmd(
null,
null,
null,
srcFofn,
procsStr,
checkerJar.getAbsolutePath(),
jdkPath,
classpathFofn,
bootClassPath,
props,
out,
true,
null);
}
/**
* Any options found under the label "Additional Compiler Options" in the Checker Framework
* Plugin preferences page
*
* @param store The preference store for this plugin
*/
private void addPreferenceOptions(final List<String> opts, IPreferenceStore store) {
// add options from preferences
String argStr = store.getString(CheckerPreferences.PREF_CHECKER_ARGS);
List<OptionLine> optionlines = OptionLine.parseOptions(argStr);
for (final OptionLine optLine : optionlines) {
if (optLine.isActive()) {
opts.add(optLine.getArgument());
}
}
}
/**
* Add options for type processing from the preferences
*
* @param opts
*/
private void addProcessorOptions(
Map<PluginUtil.CheckerProp, Object> opts, IPreferenceStore store) {
// TODO: some input validation would be nice here. Especially for
// the additional compiler flags, which could be checked against
// the compiler.
String skipUses = store.getString(CheckerPreferences.PREF_CHECKER_A_SKIP_CLASSES);
if (!skipUses.isEmpty()) {
opts.put(PluginUtil.CheckerProp.A_SKIP, skipUses);
}
String lintOpts = store.getString(CheckerPreferences.PREF_CHECKER_A_LINT);
if (!lintOpts.isEmpty()) {
opts.put(PluginUtil.CheckerProp.A_LINT, lintOpts);
}
if (store.getBoolean(CheckerPreferences.PREF_CHECKER_A_WARNS)) {
opts.put(PluginUtil.CheckerProp.A_WARNS, Boolean.TRUE);
}
if (store.getBoolean(CheckerPreferences.PREF_CHECKER_A_NO_MSG_TEXT))
opts.put(PluginUtil.CheckerProp.A_NO_MSG_TXT, Boolean.TRUE);
if (store.getBoolean(CheckerPreferences.PREF_CHECKER_A_SHOW_CHECKS))
opts.put(PluginUtil.CheckerProp.A_SHOW_CHECKS, Boolean.TRUE);
if (store.getBoolean(CheckerPreferences.PREF_CHECKER_A_FILENAMES))
opts.put(PluginUtil.CheckerProp.A_FILENAMES, Boolean.TRUE);
}
/**
* Find a file within the plugin directory
*
* @param path The name of the file to find relative to the plugin directory
* @return The path to the given file
*/
public static File locatePluginFile(String path) {
Bundle bundle = Platform.getBundle(CheckerPlugin.PLUGIN_ID);
Path checkersJAR = new Path(path);
URL checkersJarURL;
try {
checkersJarURL = FileLocator.toFileURL(FileLocator.find(bundle, checkersJAR, null));
} catch (IOException e) {
throw new RuntimeException("Exception locating plugin on path: " + path, e);
} catch (NullPointerException npe) {
throw new RuntimeException(
"Bundle= "
+ bundle
+ " ID="
+ CheckerPlugin.PLUGIN_ID
+ " checkerJar="
+ checkersJAR,
npe);
}
File checkersJarFile;
try {
checkersJarFile = new File(checkersJarURL.toURI());
} catch (URISyntaxException e) {
checkersJarFile = new File(checkersJarURL.getPath());
}
return checkersJarFile;
}
/**
* Parse the result of calling the Checker Framework compiler
*
* @return A list of JavacErrors parsed from the compiler output
*/
public List<JavacError> getErrors() {
return JavacError.parse(checkResult);
}
public static void printTrimmedOutput(final MessageConsoleStream out, final String output) {
List<String> lines = Arrays.asList(output.split(Util.NL));
for (final String line : lines) {
out.println(JavacError.trimDetails(line));
}
}
}