blob: c8518674f5b412e1f4ff0ee676cb0d7a46cca0e5 [file] [log] [blame]
package org.checkerframework.javacutil;
import com.sun.tools.javac.main.Option;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Options;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.processing.ProcessingEnvironment;
import org.checkerframework.checker.nullness.qual.Nullable;
/** This file contains basic utility functions. */
public class SystemUtil {
/**
* Returns the major JRE version.
*
* <p>This is different from the version passed to the compiler via --release; use {@link
* #getReleaseValue(ProcessingEnvironment)} to get that version.
*
* <p>Extract the major version number from the "java.version" system property. Two possible
* formats are considered. Up to Java 8, from a version string like `1.8.whatever`, this method
* extracts 8. Since Java 9, from a version string like `11.0.1`, this method extracts 11.
*
* @return the major version number from "java.version"
*/
public static int getJreVersion() {
final String jreVersionStr = System.getProperty("java.version");
final Pattern oldVersionPattern = Pattern.compile("^1\\.(\\d+)\\..*$");
final Matcher oldVersionMatcher = oldVersionPattern.matcher(jreVersionStr);
if (oldVersionMatcher.matches()) {
String v = oldVersionMatcher.group(1);
assert v != null : "@AssumeAssertion(nullness): inspection";
return Integer.parseInt(v);
}
// See http://openjdk.java.net/jeps/223
// We only care about the major version number.
final Pattern newVersionPattern = Pattern.compile("^(\\d+).*$");
final Matcher newVersionMatcher = newVersionPattern.matcher(jreVersionStr);
if (newVersionMatcher.matches()) {
String v = newVersionMatcher.group(1);
assert v != null : "@AssumeAssertion(nullness): inspection";
return Integer.parseInt(v);
}
// For Early Access version of the JDK
final Pattern eaVersionPattern = Pattern.compile("^(\\d+)-ea$");
final Matcher eaVersionMatcher = eaVersionPattern.matcher(jreVersionStr);
if (eaVersionMatcher.matches()) {
String v = eaVersionMatcher.group(1);
assert v != null : "@AssumeAssertion(nullness): inspection";
return Integer.parseInt(v);
}
throw new RuntimeException(
"Could not determine version from property java.version=" + jreVersionStr);
}
/**
* Returns the release value passed to the compiler or null if release was not passed.
*
* @param env the ProcessingEnvironment
* @return the release value or null if none was passed
*/
public static @Nullable String getReleaseValue(ProcessingEnvironment env) {
Context ctx = ((JavacProcessingEnvironment) env).getContext();
Options options = Options.instance(ctx);
return options.get(Option.RELEASE);
}
/**
* Returns the pathname to the tools.jar file, or null if it does not exist. Returns null on Java
* 9 and later.
*
* @return the pathname to the tools.jar file, or null
*/
public static @Nullable String getToolsJar() {
if (getJreVersion() > 8) {
return null;
}
String javaHome = System.getenv("JAVA_HOME");
if (javaHome == null) {
String javaHomeProperty = System.getProperty("java.home");
if (javaHomeProperty.endsWith(File.separator + "jre")) {
javaHome = javaHomeProperty.substring(javaHomeProperty.length() - 4);
} else {
// Could also determine the location of javac on the path...
throw new Error("Can't infer Java home; java.home=" + javaHomeProperty);
}
}
String toolsJarFilename = javaHome + File.separator + "lib" + File.separator + "tools.jar";
if (!new File(toolsJarFilename).exists()) {
throw new Error(
String.format(
"File does not exist: %s ; JAVA_HOME=%s ; java.home=%s",
toolsJarFilename, javaHome, System.getProperty("java.home")));
}
return javaHome + File.separator + "lib" + File.separator + "tools.jar";
}
///
/// Array and collection methods
///
/**
* Returns a list that contains all the distinct elements of the two lists: that is, the union of
* the two arguments.
*
* <p>For very short lists, this is likely more efficient than creating a set and converting back
* to a list.
*
* @param <T> the type of the list elements
* @param list1 a list
* @param list2 a list
* @return a list that contains all the distinct elements of the two lists
*/
public static <T> List<T> union(List<T> list1, List<T> list2) {
List<T> result = new ArrayList<>(list1.size() + list2.size());
addWithoutDuplicates(result, list1);
addWithoutDuplicates(result, list2);
return result;
}
/**
* Adds, to dest, all the elements of source that are not already in dest.
*
* <p>For very short lists, this is likely more efficient than creating a set and converting back
* to a list.
*
* @param <T> the type of the list elements
* @param dest a list to add to
* @param source a list of elements to add
*/
@SuppressWarnings("nullness:argument" // true positive: `dest` might be incompatible with
// null and `source` might contain null.
)
public static <T> void addWithoutDuplicates(List<T> dest, List<? extends T> source) {
for (T elt : source) {
if (!dest.contains(elt)) {
dest.add(elt);
}
}
}
/**
* Returns a list that contains all the elements that are in both lists: that is, the set
* difference of the two arguments.
*
* <p>For very short lists, this is likely more efficient than creating a set and converting back
* to a list.
*
* @param <T> the type of the list elements
* @param list1 a list
* @param list2 a list
* @return a list that contains all the elements of {@code list1} that are not in {@code list2}
*/
public static <T> List<T> intersection(List<? extends T> list1, List<? extends T> list2) {
List<T> result = new ArrayList<>(list1);
result.retainAll(list2);
return result;
}
///
/// Deprecated methods
///
/**
* Return a list of Strings, one per line of the file.
*
* @param argFile argument file
* @return a list of Strings, one per line of the file
* @throws IOException when reading the argFile
* @deprecated use Files.readAllLines
*/
@Deprecated // 2021-03-10
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<>();
while ((line = br.readLine()) != null) {
lines.add(line);
}
br.close();
return lines;
}
/**
* Like Thread.sleep, but does not throw any exceptions, so it is easier for clients to use.
* Causes the currently executing thread to sleep (temporarily cease execution) for the specified
* number of milliseconds.
*
* @param millis the length of time to sleep in milliseconds
* @deprecated use SystemPlume.sleep
*/
@Deprecated // 2021-03-10
public static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
/**
* Concatenates an element, an array, and an element.
*
* @param <T> the type of the array elements
* @param firstElt the first element
* @param array the array
* @param lastElt the last elemeent
* @return a new array containing first element, the array, and the last element, in that order
* @deprecated use PlumeUtil.concat
*/
@Deprecated // 2021-03-10
@SuppressWarnings("unchecked")
public static <T> T[] concatenate(T firstElt, T[] array, T lastElt) {
@SuppressWarnings("nullness") // elements are not non-null yet, but will be by return stmt
T[] result = Arrays.copyOf(array, array.length + 2);
result[0] = firstElt;
System.arraycopy(array, 0, result, 1, array.length);
result[result.length - 1] = lastElt;
return result;
}
/**
* Return true if the system property is set to "true". Return false if the system property is not
* set or is set to "false". Otherwise, errs.
*
* @param key system property to check
* @return true if the system property is set to "true". Return false if the system property is
* not set or is set to "false". Otherwise, errs.
* @deprecated use UtilPlume.getBooleanSystemProperty
*/
@Deprecated // 2021-03-28
public static boolean getBooleanSystemProperty(String key) {
return Boolean.parseBoolean(System.getProperty(key, "false"));
}
/**
* Return its boolean value if the system property is set. Return defaultValue if the system
* property is not set. Errs if the system property is set to a non-boolean value.
*
* @param key system property to check
* @param defaultValue value to use if the property is not set
* @return the boolean value of {@code key} or {@code defaultValue} if {@code key} is not set
* @deprecated use UtilPlume.getBooleanSystemProperty
*/
@Deprecated // 2021-03-28
public static boolean getBooleanSystemProperty(String key, boolean defaultValue) {
String value = System.getProperty(key);
if (value == null) {
return defaultValue;
}
if (value.equals("true")) {
return true;
}
if (value.equals("false")) {
return false;
}
throw new Error(
String.format(
"Value for system property %s should be boolean, but is \"%s\".", key, value));
}
/**
* Concatenates two arrays. Can be invoked varargs-style.
*
* @param <T> the type of the array elements
* @param array1 the first array
* @param array2 the second array
* @return a new array containing the contents of the given arrays, in order
* @deprecated use StringsPlume.concatenate
*/
@Deprecated // 2021-03-28
@SuppressWarnings("unchecked")
public static <T> T[] concatenate(T[] array1, T... array2) {
@SuppressWarnings("nullness") // elements are not non-null yet, but will be by return stmt
T[] result = Arrays.copyOf(array1, array1.length + array2.length);
System.arraycopy(array2, 0, result, array1.length, array2.length);
return result;
}
/**
* Given an expected number of elements, returns the capacity that should be passed to a HashMap
* or HashSet constructor, so that the set or map will not resize.
*
* @param numElements the maximum expected number of elements in the map or set
* @return the initial capacity to pass to a HashMap or HashSet constructor
* @deprecated use CollectionsPlume.mapCapacity
*/
@Deprecated // 2021-05-05
public static int mapCapacity(int numElements) {
// Equivalent to: (int) (numElements / 0.75) + 1
// where 0.75 is the default load factor.
return (numElements * 4 / 3) + 1;
}
/**
* Given an expected number of elements, returns the capacity that should be passed to a HashMap
* or HashSet constructor, so that the set or map will not resize.
*
* @param c a collection whose size is the maximum expected number of elements in the map or set
* @return the initial capacity to pass to a HashMap or HashSet constructor
* @deprecated use CollectionsPlume.mapCapacity
*/
@Deprecated // 2021-05-05
public static int mapCapacity(Collection<?> c) {
return mapCapacity(c.size());
}
/**
* Given an expected number of elements, returns the capacity that should be passed to a HashMap
* or HashSet constructor, so that the set or map will not resize.
*
* @param m a map whose size is the maximum expected number of elements in the map or set
* @return the initial capacity to pass to a HashMap or HashSet constructor
* @deprecated use CollectionsPlume.mapCapacity
*/
@Deprecated // 2021-05-05
public static int mapCapacity(Map<?, ?> m) {
return mapCapacity(m.size());
}
}