package org.checkerframework.framework.util;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This file contains basic utility functions that should be reused to create a command-line call to
* {@code CheckerMain}.
*
* <p>NOTE: There are multiple copies of this file in the following projects/locations:
*
* <pre>
* checker-framework-eclipse-plugin/
* org.checkerframework.eclipse.util.PluginUtil
*
* checker-framework/
* org.checkerframework.framework.util.PluginUtil
* </pre>
*
* These files MUST be IDENTICAL after the package descriptor.
*/
public class PluginUtil {
/**
* Option name for specifying an alternative javac.jar location. The accompanying value MUST be
* the path to the jar file (NOT the path to its encompassing directory)
*/
public static final String JAVAC_PATH_OPT = "-javacJar";
/**
* Option name for specifying an alternative jdk.jar location. The accompanying value MUST be
* the path to the jar file (NOT the path to its encompassing directory)
*/
public static final String JDK_PATH_OPT = "-jdkJar";
public static List<File> toFiles(final List<String> fileNames) {
final List<File> files = new ArrayList<File>(fileNames.size());
for (final String fn : fileNames) {
files.add(new File(fn));
}
return files;
}
/**
* Takes a list of files and writes it as a "File of file names" (i.e. a file with one filepath
* on each line) to the destination file, overwriting the destination file if it exists. Note
* the filepath used is the absolute filepath
*
* @param destination the fofn file we are writing. This file will contain newline separated
* list of absolute file paths.
* @param files the files to write to the destination file
*/
public static void writeFofn(final File destination, final List<File> files)
throws IOException {
final BufferedWriter bw = new BufferedWriter(new FileWriter(destination));
try {
for (final File file : files) {
bw.write(wrapArg(file.getAbsolutePath()));
bw.newLine();
}
bw.flush();
} finally {
bw.close();
}
}
/**
* Takes a list of files and writes it as a "File of file names" (i.e. a file with one filepath
* on each line) to the destination file, overwriting the destination file if it exists. Note
* the filepath used is the absolute filepath
*
* @param destination the fofn file we are writing. This file will contain newline separated
* list of absolute file paths.
* @param files the files to write to the destination file
*/
public static void writeFofn(final File destination, final File... files) throws IOException {
writeFofn(destination, Arrays.asList(files));
}
public static File writeTmpFofn(
final String prefix,
final String suffix,
final boolean deleteOnExit,
final List<File> files)
throws IOException {
final File tmpFile = File.createTempFile(prefix, suffix);
if (deleteOnExit) {
tmpFile.deleteOnExit();
}
writeFofn(tmpFile, files);
return tmpFile;
}
/**
* Write the strings to a temporary file.
*
* @param deleteOnExit if true, delete the file on program exit
*/
public static File writeTmpFile(
final String prefix,
final String suffix,
final boolean deleteOnExit,
final List<String> args)
throws IOException {
final File tmpFile = File.createTempFile(prefix, suffix);
if (deleteOnExit) {
tmpFile.deleteOnExit();
}
writeFile(tmpFile, args);
return tmpFile;
}
/** Write the strings to the file, one per line. */
public static void writeFile(final File destination, final List<String> contents)
throws IOException {
final BufferedWriter bw = new BufferedWriter(new FileWriter(destination));
try {
for (String line : contents) {
bw.write(line);
bw.newLine();
}
bw.flush();
} finally {
bw.close();
}
}
/** Return a list of Strings, one per line of the file. */
public static List<String> readFile(final File argFile) throws IOException {
final BufferedReader br = new BufferedReader(new FileReader(argFile));
String line;
List<String> lines = new ArrayList<String>();
while ((line = br.readLine()) != null) {
lines.add(line);
}
br.close();
return lines;
}
public static <T> String join(final String delimiter, final T[] objs) {
boolean notFirst = false;
final StringBuffer sb = new StringBuffer();
for (final Object obj : objs) {
if (notFirst) {
sb.append(delimiter);
}
sb.append(obj.toString());
notFirst = true;
}
return sb.toString();
}
public static String join(String delimiter, Iterable<?> values) {
StringBuilder sb = new StringBuilder();
boolean isntFirst = false;
for (Object value : values) {
if (isntFirst) {
sb.append(delimiter);
}
sb.append(value);
isntFirst = true;
}
return sb.toString();
}
public static List<String> getStringProp(
final Map<CheckerProp, Object> props,
final CheckerProp prop,
final String tag,
final String... extras) {
final List<String> out = new ArrayList<String>();
final String strProp = (String) props.get(prop);
if (strProp != null && !strProp.isEmpty()) {
out.add(tag + strProp);
for (final String extra : extras) {
out.add(extra);
}
}
return out;
}
public static List<String> getBooleanProp(
final Map<CheckerProp, Object> props, final CheckerProp prop, final String tag) {
Boolean aSkip = (Boolean) props.get(prop);
if (aSkip != null && aSkip) {
return Arrays.asList(tag);
}
return new ArrayList<String>();
}
public enum CheckerProp {
IMPLICIT_IMPORTS() {
@Override
public List<String> getCmdLine(final Map<CheckerProp, Object> props) {
return getStringProp(props, this, "-J-Djsr308_imports=", "-implicit:class");
}
},
MISC_COMPILER() {
@Override
public List<String> getCmdLine(final Map<CheckerProp, Object> props) {
@SuppressWarnings("unchecked")
List<String> miscOpts = (List<String>) props.get(this);
if (miscOpts != null && !miscOpts.isEmpty()) {
return new ArrayList<String>(miscOpts);
}
return new ArrayList<String>();
}
},
A_SKIP() {
@Override
public List<String> getCmdLine(final Map<CheckerProp, Object> props) {
return getStringProp(props, this, "-AskipUses=");
}
},
A_LINT() {
@Override
public List<String> getCmdLine(final Map<CheckerProp, Object> props) {
return getStringProp(props, this, "-Alint=");
}
},
A_WARNS() {
@Override
public List<String> getCmdLine(final Map<CheckerProp, Object> props) {
return getBooleanProp(props, this, "-Awarns");
}
},
A_NO_MSG_TXT() {
@Override
public List<String> getCmdLine(final Map<CheckerProp, Object> props) {
return getBooleanProp(props, this, "-Anomsgtext");
}
},
A_SHOW_CHECKS() {
@Override
public List<String> getCmdLine(final Map<CheckerProp, Object> props) {
return getBooleanProp(props, this, "-Ashowchecks");
}
},
A_FILENAMES() {
@Override
public List<String> getCmdLine(final Map<CheckerProp, Object> props) {
return getBooleanProp(props, this, "-Afilenames");
}
},
A_DETAILED_MSG() {
@Override
public List<String> getCmdLine(final Map<CheckerProp, Object> props) {
return getBooleanProp(props, this, "-Adetailedmsgtext");
}
};
public abstract List<String> getCmdLine(final Map<CheckerProp, Object> props);
}
/**
* Any options found in props to the cmd list
*
* @param cmd a list to which the options should be added
* @param props the map of checker properties too search for options in
*/
private static void addOptions(final List<String> cmd, Map<CheckerProp, Object> props) {
for (CheckerProp cp : CheckerProp.values()) {
cmd.addAll(cp.getCmdLine(props));
}
}
public static File writeTmpSrcFofn(
final String prefix, final boolean deleteOnExit, final List<File> files)
throws IOException {
return writeTmpFofn(prefix, ".src_files", deleteOnExit, files);
}
public static File writeTmpCpFile(
final String prefix, final boolean deleteOnExit, final String classpath)
throws IOException {
return writeTmpFile(
prefix,
".classpath",
deleteOnExit,
Arrays.asList("-classpath", wrapArg(classpath)));
}
public static boolean isWindows() {
final String os = System.getProperty("os.name");
return os.toLowerCase().contains("win");
}
public static String wrapArg(final String classpath) {
if (classpath.contains(" ")) {
return '"' + escapeQuotesAndSlashes(classpath) + '"';
}
return classpath;
}
public static String escapeQuotesAndSlashes(final String toEscape) {
final Map<String, String> replacements = new HashMap<String, String>();
replacements.put("\\\\", "\\\\\\\\");
replacements.put("\"", "\\\\\"");
String replacement = toEscape;
for (final Map.Entry<String, String> entry : replacements.entrySet()) {
replacement = replacement.replaceAll(entry.getKey(), entry.getValue());
}
return replacement;
}
public static String getJavaCommand(final String javaHome, final PrintStream out) {
if (javaHome == null || javaHome.equals("")) {
return "java";
}
final File java = new File(javaHome, "bin" + File.separator + "java");
final File javaExe = new File(javaHome, "bin" + File.separator + "java.exe");
if (java.exists()) {
return java.getAbsolutePath();
} else if (javaExe.exists()) {
return javaExe.getAbsolutePath();
} else {
if (out != null) {
out.println(
"Could not find java executable at: ( "
+ java.getAbsolutePath()
+ ","
+ javaExe.getAbsolutePath()
+ ")"
+ "\n Using \"java\" command.\n");
}
return "java";
}
}
public static String fileArgToStr(final File fileArg) {
return "@" + fileArg.getAbsolutePath();
}
//TODO: Perhaps unify this with CheckerMain as it violates DRY
public static List<String> getCmd(
final String executable,
final File javacPath,
final File jdkPath,
final File srcFofn,
final String processors,
final String checkerHome,
final String javaHome,
final File classPathFofn,
final String bootClassPath,
final Map<CheckerProp, Object> props,
PrintStream out,
final boolean procOnly,
final String outputDirectory) {
final List<String> cmd = new ArrayList<String>();
final String java = (executable != null) ? executable : getJavaCommand(javaHome, out);
cmd.add(java);
cmd.add("-jar");
cmd.add(checkerHome);
if (procOnly) {
cmd.add("-proc:only");
} else if (outputDirectory != null) {
cmd.add("-d");
cmd.add(outputDirectory);
}
if (bootClassPath != null && !bootClassPath.trim().isEmpty()) {
cmd.add("-Xbootclasspath/p:" + bootClassPath);
}
if (javacPath != null) {
cmd.add(JAVAC_PATH_OPT);
cmd.add(javacPath.getAbsolutePath());
}
if (jdkPath != null) {
cmd.add(JDK_PATH_OPT);
cmd.add(jdkPath.getAbsolutePath());
}
if (classPathFofn != null) {
cmd.add(fileArgToStr(classPathFofn));
}
if (processors != null) {
cmd.add("-processor");
cmd.add(processors);
}
addOptions(cmd, props);
cmd.add(fileArgToStr(srcFofn));
return cmd;
}
public static List<String> toJavaOpts(final List<String> opts) {
final List<String> outOpts = new ArrayList<String>(opts.size());
for (final String opt : opts) {
outOpts.add("-J" + opt);
}
return outOpts;
}
public static List<String> getCmdArgsOnly(
final File srcFofn,
final String processors,
final String checkerHome,
final String javaHome,
final File classpathFofn,
final String bootClassPath,
final Map<CheckerProp, Object> props,
PrintStream out,
final boolean procOnly,
final String outputDirectory) {
final List<String> cmd =
getCmd(
null,
null,
null,
srcFofn,
processors,
checkerHome,
javaHome,
classpathFofn,
bootClassPath,
props,
out,
procOnly,
outputDirectory);
cmd.remove(0);
return cmd;
}
public static List<String> getCmdArgsOnly(
final File javacPath,
final File jdkPath,
final File srcFofn,
final String processors,
final String checkerHome,
final String javaHome,
final File classpathFofn,
final String bootClassPath,
final Map<CheckerProp, Object> props,
PrintStream out,
final boolean procOnly,
final String outputDirectory) {
final List<String> cmd =
getCmd(
null,
javacPath,
jdkPath,
srcFofn,
processors,
checkerHome,
javaHome,
classpathFofn,
bootClassPath,
props,
out,
procOnly,
outputDirectory);
cmd.remove(0);
return cmd;
}
/**
* Extract the first two version numbers from java.version (e.g. 1.6 from 1.6.whatever)
*
* @return the first two version numbers from java.version (e.g. 1.6 from 1.6.whatever)
*/
public static double getJreVersion() {
final String jreVersionStr = System.getProperty("java.version");
final Pattern versionPattern = Pattern.compile("^(\\d\\.\\d+)\\..*$");
final Matcher versionMatcher = versionPattern.matcher(jreVersionStr);
// For Early Access version of the JDK
final Pattern eaVersionPattern = Pattern.compile("^(\\d+)-ea$");
final Matcher eaVersionMatcher = eaVersionPattern.matcher(jreVersionStr);
final double version;
if (versionMatcher.matches()) {
version = Double.parseDouble(versionMatcher.group(1));
} else if (eaVersionMatcher.matches()) {
version = Double.parseDouble("1." + eaVersionMatcher.group(1));
} else {
throw new RuntimeException(
"Could not determine version from property java.version=" + jreVersionStr);
}
return version;
}
/**
* Determine the version of the JRE that we are currently running and select a jdkX where X is
* the version of Java that is being run (e.g. 6, 7, ...)
*
* @return "jdk<em>X</em>" where X is the version of Java that is being run (e.g. 6, 7, ...)
*/
public static String getJdkJarPrefix() {
final double jreVersion = getJreVersion();
final String prefix;
if (jreVersion == 1.7) {
prefix = "jdk7";
} else if (jreVersion == 1.8) {
prefix = "jdk8";
} else if (jreVersion == 1.9) {
prefix = "jdk9";
} else {
throw new AssertionError("Unsupported JRE version: " + jreVersion);
}
return prefix;
}
/**
* Determine the version of the JRE that we are currently running and select a jdkX.jar where X
* is the version of Java that is being run (e.g. 6, 7, ...)
*
* @return the jdkX.jar where X is the version of Java that is being run (e.g. 6, 7, ...)
*/
public static String getJdkJarName() {
final String fileName = getJdkJarPrefix() + ".jar";
return fileName;
}
}