All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.checkerframework.framework.util.CheckerMain Maven / Gradle / Ivy

Go to download

The Checker Framework enhances Java's type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.0.0-b2
Show newest version
package org.checkerframework.framework.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.jar.JarInputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.PluginUtil;

/**
 * This class behaves similarly to javac. CheckerMain does the following:
 *
 * 
    *
  • add the {@code javac.jar} to the runtime classpath of the process that runs the Checker * Framework. *
  • add {@code jdk8.jar} to the compile time bootclasspath of the javac argument list passed to * javac *
  • parse and implement any special options used by the Checker Framework, e.g., using * "shortnames" for annotation processors *
  • pass all remaining command-line arguments to the real javac *
* * To debug this class, use the {@code -AoutputArgsToFile=FILENAME} command-line argument or {@code * -AoutputArgsToFile=-} to output to standard out. * *

"To run the Checker Framework" really means to run java, where the program being run is a * special version of javac, and javac is passed a {@code -processor} command-line argument that * mentions a Checker Framework checker. There are 5 relevant classpaths: The classpath and * bootclasspath when running java, and the classpath, bootclasspath, and processorpath used by * javac. The latter three are the only important ones. * *

Note for developers: Try to limit the work done (and options interpreted) by CheckerMain, * because its functionality is not available to users who choose not to use the Checker Framework * javac script. */ public class CheckerMain { /** Any exception thrown by the Checker Framework escapes to the command line. */ public static void main(String[] args) { final File pathToThisJar = new File(findPathTo(CheckerMain.class, false)); ArrayList alargs = new ArrayList<>(args.length); alargs.addAll(Arrays.asList(args)); final CheckerMain program = new CheckerMain(pathToThisJar, alargs); final int exitStatus = program.invokeCompiler(); System.exit(exitStatus); } /** The path to the annotated jdk jar to use. */ protected final File jdkJar; /** The path to the javacJar to use. */ protected final File javacJar; /** The path to the jar containing CheckerMain.class (i.e. checker.jar). */ protected final File checkerJar; /** The path to checker-qual.jar. */ protected final File checkerQualJar; private final List compilationBootclasspath; private final List runtimeClasspath; private final List jvmOpts; /** * Each element is either a classpath element (a directory or jar file) or is a classpath * (containing elements separated by File.pathSeparator). To produce the final classpath, * concatenate them all (separated by File.pathSeparator). */ private final List cpOpts; private final List ppOpts; private final List toolOpts; private final List argListFiles; /** * Construct all the relevant file locations and Java version given the path to this jar and a * set of directories in which to search for jars. */ public CheckerMain(final File checkerJar, final List args) { this.checkerJar = checkerJar; final File searchPath = checkerJar.getParentFile(); replaceShorthandProcessor(args); argListFiles = collectArgFiles(args); this.checkerQualJar = extractFileArg( PluginUtil.CHECKER_QUAL_PATH_OPT, new File(searchPath, "checker-qual.jar"), args); this.javacJar = extractFileArg(PluginUtil.JAVAC_PATH_OPT, new File(searchPath, "javac.jar"), args); final String jdkJarName = PluginUtil.getJdkJarName(); this.jdkJar = extractFileArg(PluginUtil.JDK_PATH_OPT, new File(searchPath, jdkJarName), args); this.compilationBootclasspath = createCompilationBootclasspath(args); this.runtimeClasspath = createRuntimeClasspath(args); this.jvmOpts = extractJvmOpts(args); this.cpOpts = createCpOpts(args); this.ppOpts = createPpOpts(args); this.toolOpts = args; assertValidState(); } protected void assertValidState() { assertFilesExist(Arrays.asList(javacJar, jdkJar, checkerJar, checkerQualJar)); } public void addToClasspath(List cpOpts) { this.cpOpts.addAll(cpOpts); } public void addToProcessorpath(List ppOpts) { this.ppOpts.addAll(ppOpts); } public void addToRuntimeClasspath(List runtimeClasspathOpts) { this.runtimeClasspath.addAll(runtimeClasspathOpts); } protected List createRuntimeClasspath(final List argsList) { return new ArrayList<>(Arrays.asList(javacJar.getAbsolutePath())); } protected List createRuntimeBootclasspath(final List argsList) { return new ArrayList<>(Arrays.asList(javacJar.getAbsolutePath())); } protected List createCompilationBootclasspath(final List argsList) { final List extractedBcp = extractBootClassPath(argsList); extractedBcp.add(0, jdkJar.getAbsolutePath()); return extractedBcp; } protected List createCpOpts(final List argsList) { final List extractedOpts = extractCpOpts(argsList); extractedOpts.add(0, this.checkerQualJar.getAbsolutePath()); return extractedOpts; } // Assumes that createCpOpts has already been run. protected List createPpOpts(final List argsList) { final List extractedOpts = extractPpOpts(argsList); if (extractedOpts.isEmpty()) { // If processorpath is not provided, then javac uses the classpath. // CheckerMain always supplies a processorpath, so if the user // didn't specify a processorpath, then use the classpath. extractedOpts.addAll(this.cpOpts); } extractedOpts.add(0, this.checkerJar.getAbsolutePath()); return extractedOpts; } /** * Return the arguments that start with @ and therefore are files that contain javac arguments. * * @param args a list of command-line arguments; is not modified * @return a List of files representing all arguments that started with @ */ protected List collectArgFiles(final List args) { final List argListFiles = new ArrayList<>(); for (final String arg : args) { if (arg.startsWith("@")) { argListFiles.add(new File(arg.substring(1))); } } return argListFiles; } /** * Remove the argument given by argumentName and the subsequent value from the list args if * present. Return the subsequent value. * * @param argumentName a command-line option name whose argument to extract * @param alternative default value to return if argumentName does not appear in args * @param args the current list of arguments * @return the string that follows argumentName if argumentName is in args, or alternative if * argumentName is not present in args */ protected static String extractArg( final String argumentName, final String alternative, final List args) { int i = args.indexOf(argumentName); if (i == -1) { return alternative; } else if (i == args.size() - 1) { throw new BugInCF( "Command line contains " + argumentName + " but no value following it"); } else { args.remove(i); return args.remove(i); } } /** * Remove the argument given by argumentName and the subsequent value from the list args if * present. Return the subsequent value wrapped as a File. * * @param argumentName argument to extract * @param alternative file to return if argumentName is not found in args * @param args the current list of arguments * @return the string that follows argumentName wrapped as a File if argumentName is in args or * alternative if argumentName is not present in args */ protected static File extractFileArg( final String argumentName, final File alternative, final List args) { final String filePath = extractArg(argumentName, null, args); if (filePath == null) { return alternative; } else { return new File(filePath); } } /** * Find all args that match the given pattern and extract their index 1 group. Add all the index * 1 groups to the returned list. Remove all matching args from the input args list. * * @param pattern a pattern with at least one matching group * @param allowEmpties whether or not to add empty group(1) matches to the returned list * @param args the arguments to extract from * @return a list of arguments from the first group that matched the pattern for each input args * or the empty list if there were none */ protected static List extractOptWithPattern( final Pattern pattern, boolean allowEmpties, final List args) { final List matchedArgs = new ArrayList<>(); int i = 0; while (i < args.size()) { final Matcher matcher = pattern.matcher(args.get(i)); if (matcher.matches()) { final String arg = matcher.group(1).trim(); if (!arg.isEmpty() || allowEmpties) { matchedArgs.add(arg); } args.remove(i); } else { i++; } } return matchedArgs; } /** * A pattern to match bootclasspath prepend entries, used to construct one {@code * -Xbootclasspath/p:} command-line argument. */ protected static final Pattern BOOT_CLASS_PATH_REGEX = Pattern.compile("^(?:-J)?-Xbootclasspath/p:(.*)$"); // TODO: Why does this treat -J and -J-X the same? They have different semantics, don't they? /** * Remove all {@code -Xbootclasspath/p:} or {@code -J-Xbootclasspath/p:} arguments from args and * add them to the returned list. * * @param args the arguments to extract from * @return all non-empty arguments matching BOOT_CLASS_PATH_REGEX or an empty list if there were * none */ protected static List extractBootClassPath(final List args) { return extractOptWithPattern(BOOT_CLASS_PATH_REGEX, false, args); } /** Matches all {@code -J} arguments. */ protected static final Pattern JVM_OPTS_REGEX = Pattern.compile("^(?:-J)(.*)$"); /** * Remove all {@code -J} arguments from {@code args} and add them to the returned list (without * the {@code -J} prefix). * * @param args the arguments to extract from * @return all {@code -J} arguments (without the {@code -J} prefix) or an empty list if there * were none */ protected static List extractJvmOpts(final List args) { return extractOptWithPattern(JVM_OPTS_REGEX, false, args); } /** * Return the last {@code -cp} or {@code -classpath} option. If no {@code -cp} or {@code * -classpath} arguments were present, then return the CLASSPATH environment variable (if set) * followed by the current directory. * *

Also removes all {@code -cp} and {@code -classpath} options from args. * * @param args a list of arguments to extract from; is side-effected by this * @return collection of classpaths to concatenate to use when calling javac.jar */ protected static List extractCpOpts(final List args) { List actualArgs = new ArrayList<>(); String lastCpArg = null; for (int i = 0; i < args.size(); i++) { if ((args.get(i).equals("-cp") || args.get(i).equals("-classpath")) && (i + 1 < args.size())) { args.remove(i); // Every classpath entry overrides the one before it. lastCpArg = args.remove(i); // re-process whatever is currently at element i i--; } } // The logic below is exactly what the javac script does. If no command-line classpath is // specified, use the "CLASSPATH" environment variable followed by the current directory. if (lastCpArg == null) { final String systemClassPath = System.getenv("CLASSPATH"); if (systemClassPath != null && !systemClassPath.trim().isEmpty()) { actualArgs.add(systemClassPath.trim()); } actualArgs.add("."); } else { actualArgs.add(lastCpArg); } return actualArgs; } /** * Remove the {@code -processorpath} options and their arguments from args. Return the last * argument. * * @param args a list of arguments to extract from * @return the arguments that should be put on the processorpath when calling javac.jar */ protected static List extractPpOpts(final List args) { List actualArgs = new ArrayList<>(); String path = null; for (int i = 0; i < args.size(); i++) { if (args.get(i).equals("-processorpath") && (i + 1 < args.size())) { args.remove(i); path = args.remove(i); // re-process whatever is currently at element i i--; } } if (path != null) { actualArgs.add(path); } return actualArgs; } protected void addMainToArgs(final List args) { args.add("com.sun.tools.javac.Main"); } /** Invoke the compiler with all relevant jars on its classpath and/or bootclasspath. */ // TODO: unify with PluginUtil.getCmd public List getExecArguments() { List args = new ArrayList<>(jvmOpts.size() + cpOpts.size() + toolOpts.size() + 7); // TODO: do we need java.exe on Windows? final String java = "java"; // PluginUtil.getJavaCommand(System.getProperty("java.home"), System.out); args.add(java); if (PluginUtil.getJreVersion() == 8) { args.add("-Xbootclasspath/p:" + PluginUtil.join(File.pathSeparator, runtimeClasspath)); } else { args.addAll( Arrays.asList( "--add-opens", "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", "--add-opens", "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", "--add-opens", "jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED", "--add-opens", "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", "--add-opens", "jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED", "--add-opens", "jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", "--add-opens", "jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED", "--add-opens", "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", "--add-opens", "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED")); } args.add("-classpath"); args.add(String.join(File.pathSeparator, runtimeClasspath)); args.add("-ea"); // com.sun.tools needs to be enabled separately args.add("-ea:com.sun.tools..."); args.addAll(jvmOpts); addMainToArgs(args); if (!argsListHasClassPath(argListFiles)) { args.add("-classpath"); args.add(quote(concatenatePaths(cpOpts))); } if (!argsListHasProcessorPath(argListFiles)) { args.add("-processorpath"); args.add(quote(concatenatePaths(ppOpts))); } if (PluginUtil.getJreVersion() == 8) { // No classes on the compilation bootclasspath will be loaded // during compilation, but the classes are read by the compiler // without loading them. The compiler assumes that any class on // this bootclasspath will be on the bootclasspath of the JVM used // to later run the classfiles that Javac produces. Our // jdk8.jar classes don't have bodies, so they won't be used at // run time, but other, real definitions of those classes will be // on the classpath at run time. args.add( "-Xbootclasspath/p:" + String.join(File.pathSeparator, compilationBootclasspath)); // We currently provide a Java 8 JDK and want to be runnable // on a Java 8 JVM. So set source/target to 8. args.add("-source"); args.add("8"); args.add("-target"); args.add("8"); } args.addAll(toolOpts); return args; } /** Given a list of paths, concatenate them to form a single path. Also expand wildcards. */ private String concatenatePaths(List paths) { List elements = new ArrayList<>(); for (String path : paths) { for (String element : path.split(File.pathSeparator)) { elements.addAll(expandWildcards(element)); } } return String.join(File.pathSeparator, elements); } /** The string "/*" (on Unix). */ private static final String FILESEP_STAR = File.separator + "*"; /** * Given a path element that might be a wildcard, return a list of the elements it expands to. * If the element isn't a wildcard, return a singleton list containing the argument. */ private List expandWildcards(String pathElement) { if (pathElement.equals("*")) { return jarFiles("."); } else if (pathElement.endsWith(FILESEP_STAR)) { return jarFiles(pathElement.substring(0, pathElement.length() - 1)); } else if (pathElement.equals("")) { return Collections.emptyList(); } else { return Collections.singletonList(pathElement); } } /** Return all the .jar and .JAR files in the given directory. */ private List jarFiles(String directory) { File dir = new File(directory); File[] jarFiles = dir.listFiles((d, name) -> name.endsWith(".jar") || name.endsWith(".JAR")); List result = new ArrayList<>(jarFiles.length); for (File jarFile : jarFiles) { result.add(jarFile.toString()); } return result; } /** Invoke the compiler with all relevant jars on its classpath and/or bootclasspath. */ public int invokeCompiler() { List args = getExecArguments(); for (int i = 0; i < args.size(); i++) { String arg = args.get(i); if (arg.startsWith("-AoutputArgsToFile=")) { String fileName = arg.substring(19); args.remove(i); outputArgumentsToFile(fileName, args); break; } } // Actually invoke the compiler return ExecUtil.execute(args.toArray(new String[args.size()]), System.out, System.err); } private static void outputArgumentsToFile(String outputFilename, List args) { if (outputFilename != null) { String errorMessage = null; try { PrintWriter writer = (outputFilename.equals("-") ? new PrintWriter(System.out) : new PrintWriter(outputFilename, "UTF-8")); for (int i = 0; i < args.size(); i++) { String arg = args.get(i); // We would like to include the filename of the argfile instead of its contents. // The problem is that the file will sometimes disappear by the time the user // can look at or run the resulting script. Maven deletes the argfile very // shortly after it has been handed off to javac, for example. Ideally we would // print the argfile filename as a comment but the resulting file couldn't then // be run as a script on Unix or Windows. if (arg.startsWith("@")) { // Read argfile and include its parameters in the output file. String inputFilename = arg.substring(1); BufferedReader br = new BufferedReader(new FileReader(inputFilename)); String line; while ((line = br.readLine()) != null) { writer.print(line); writer.print(" "); } br.close(); } else { writer.print(arg); writer.print(" "); } } writer.close(); } catch (IOException e) { errorMessage = e.toString(); } if (errorMessage != null) { System.err.println( "Failed to output command-line arguments to file " + outputFilename + " due to exception: " + errorMessage); } } } /** * Returns true if some @arglist file sets the classpath. * * @param argListFiles command-line argument files (specified with @ on the command line) */ private static boolean argsListHasClassPath(final List argListFiles) { for (final String arg : expandArgFiles(argListFiles)) { if (arg.contains("-classpath") || arg.contains("-cp")) { return true; } } return false; } /** * Returns true if some @arglist file sets the processorpath. * * @param argListFiles command-line argument files (specified with @ on the command line) */ private static boolean argsListHasProcessorPath(final List argListFiles) { for (final String arg : expandArgFiles(argListFiles)) { if (arg.contains("-processorpath")) { return true; } } return false; } /** * Return all the lines in all the files. * * @param files a list of files * @return a list of all the lines in all the files */ protected static List expandArgFiles(final List files) { final List content = new ArrayList<>(); for (final File file : files) { try { content.addAll(PluginUtil.readFile(file)); } catch (final IOException exc) { throw new RuntimeException("Could not open file: " + file.getAbsolutePath(), exc); } } return content; } /** * Find the jar file or directory containing the .class file from which cls was loaded. * * @param cls the class whose .class file we wish to locate; if null, CheckerMain.class. * @param errIfFromDirectory if false, throw an exception if the file was loaded from a * directory */ public static String findPathTo(Class cls, boolean errIfFromDirectory) throws IllegalStateException { if (cls == null) { cls = CheckerMain.class; } String name = cls.getName(); String classFileName; /* name is something like package.name.ContainingClass$ClassName. We need to turn this into ContainingClass$ClassName.class. */ { int idx = name.lastIndexOf('.'); classFileName = (idx == -1 ? name : name.substring(idx + 1)) + ".class"; } String uri = cls.getResource(classFileName).toString(); if (uri.startsWith("file:")) { if (errIfFromDirectory) { return uri; } else { throw new IllegalStateException( "This class has been loaded from a directory and not from a jar file."); } } if (!uri.startsWith("jar:file:")) { int idx = uri.indexOf(':'); String protocol = idx == -1 ? "(unknown)" : uri.substring(0, idx); throw new IllegalStateException( "This class has been loaded remotely via the " + protocol + " protocol. Only loading from a jar on the local file system is supported."); } int idx = uri.indexOf('!'); // Sanity check if (idx == -1) { throw new IllegalStateException( "You appear to have loaded this class from a local jar file, but I can't make sense of the URL!"); } try { String fileName = URLDecoder.decode( uri.substring("jar:file:".length(), idx), Charset.defaultCharset().name()); return new File(fileName).getAbsolutePath(); } catch (UnsupportedEncodingException e) { throw new BugInCF("Default charset doesn't exist. Your VM is borked."); } } /** * Assert that all files in the list exist and if they don't, throw a RuntimeException with a * list of the files that do not exist. * * @param expectedFiles files that must exist */ private static void assertFilesExist(final List expectedFiles) { final List missingFiles = new ArrayList<>(); for (final File file : expectedFiles) { if (file == null) { throw new RuntimeException("Null passed to assertFilesExist"); } if (!file.exists()) { missingFiles.add(file); } } if (!missingFiles.isEmpty()) { List missingAbsoluteFilenames = new ArrayList<>(missingFiles.size()); for (File missingFile : missingFiles) { missingAbsoluteFilenames.add(missingFile.getAbsolutePath()); } throw new RuntimeException( "The following files could not be located: " + String.join(", ", missingAbsoluteFilenames)); } } private static String quote(final String str) { if (str.contains(" ")) { if (str.contains("\"")) { throw new BugInCF( "Don't know how to quote a string containing a double-quote character " + str); } return "\"" + str + "\""; } return str; } /////////////////////////////////////////////////////////////////////////// /// Shorthand checker names /// /** * All "built-in" Checker Framework checkers, except SubtypingChecker, start with this package * file path. Framework Checkers, except for SubtypingChecker, are excluded from processor * shorthand. */ protected static final String CHECKER_BASE_PACKAGE = "org.checkerframework.checker"; // Forward slash is used instead of File.separator because checker.jar uses / as the separator. protected static final String CHECKER_BASE_DIR_NAME = CHECKER_BASE_PACKAGE.replace(".", "/"); protected static final String FULLY_QUALIFIED_SUBTYPING_CHECKER = org.checkerframework.common.subtyping.SubtypingChecker.class.getCanonicalName(); protected static final String SUBTYPING_CHECKER_NAME = org.checkerframework.common.subtyping.SubtypingChecker.class.getSimpleName(); /** * Returns true if processorString, once transformed into fully-qualified form, is present in * fullyQualifiedCheckerNames. Used by SourceChecker to determine whether a class is annotated * for any processor that is being run. * * @param processorString the name of a single processor, not a comma-separated list of * processors * @param fullyQualifiedCheckerNames a list of fully-qualified checker names */ public static boolean matchesCheckerOrSubcheckerFromList( final String processorString, List fullyQualifiedCheckerNames) { if (processorString.contains(",")) { return false; // Do not process strings containing multiple processors. } return fullyQualifiedCheckerNames.contains( unshorthandProcessorNames(processorString, fullyQualifiedCheckerNames, true)); } /** * For every "-processor" argument in args, replace its immediate successor argument using * unabbreviateProcessorNames. */ protected void replaceShorthandProcessor(final List args) { for (int i = 0; i < args.size(); i++) { final int nextIndex = i + 1; if (args.size() > nextIndex) { if (args.get(i).equals("-processor")) { final String replacement = unshorthandProcessorNames( args.get(nextIndex), getAllCheckerClassNames(), false); args.remove(nextIndex); args.add(nextIndex, replacement); } } } } /** * Returns the list of fully qualified names of the checkers found in checker.jar. This covers * only checkers with the name ending in "Checker". Checkers with a name ending in "Subchecker" * are not included in the returned list. Note however that it is possible for a checker with * the name ending in "Checker" to be used as a subchecker. */ private List getAllCheckerClassNames() { ArrayList checkerClassNames = new ArrayList<>(); try { final JarInputStream checkerJarIs = new JarInputStream(new FileInputStream(checkerJar)); ZipEntry entry; while ((entry = checkerJarIs.getNextEntry()) != null) { final String name = entry.getName(); // Checkers ending in "Subchecker" are not included in this list used by // CheckerMain. if (name.startsWith(CHECKER_BASE_DIR_NAME) && name.endsWith("Checker.class")) { // Forward slash is used instead of File.separator because checker.jar uses / as // the separator. checkerClassNames.add( PluginUtil.join( ".", name.substring(0, name.length() - ".class".length()) .split("/"))); } } checkerJarIs.close(); } catch (IOException e) { // When using CheckerDevelMain we might not have a checker.jar file built yet. // Issue a warning instead of aborting execution. System.err.printf( "Could not read %s. Shorthand processor names will not work.%n", checkerJar); } return checkerClassNames; } /** * Takes a string of comma-separated processor names, and expands any shorthands to * fully-qualified names from the fullyQualifiedCheckerNames list. For example: * *

     * NullnessChecker → org.checkerframework.checker.nullness.NullnessChecker
     * nullness → org.checkerframework.checker.nullness.NullnessChecker
     * NullnessChecker,RegexChecker → org.checkerframework.checker.nullness.NullnessChecker,org.checkerframework.checker.regex.RegexChecker
     * 
* * Note, a processor entry only gets replaced if it contains NO "." (i.e., it is not qualified * by a package name) and can be found under the package org.checkerframework.checker in * checker.jar. * * @param processorsString a comma-separated string identifying processors * @param fullyQualifiedCheckerNames a list of fully-qualified checker names to match * processorsString against * @param allowSubcheckers whether to match against fully qualified checker names ending with * "Subchecker" * @return processorsString where all shorthand references to Checker Framework built-in * checkers are replaced with fully-qualified references */ protected static String unshorthandProcessorNames( final String processorsString, List fullyQualifiedCheckerNames, boolean allowSubcheckers) { final String[] processors = processorsString.split(","); for (int i = 0; i < processors.length; i++) { if (processors[i].equals(SUBTYPING_CHECKER_NAME)) { // Allow "subtyping" as well. processors[i] = FULLY_QUALIFIED_SUBTYPING_CHECKER; } else { if (!processors[i].contains(".")) { // Not already fully qualified processors[i] = unshorthandProcessorName( processors[i], fullyQualifiedCheckerNames, allowSubcheckers); } } } return PluginUtil.join(",", processors); } /** * Given a processor name, tries to expand it to a checker in the fullyQualifiedCheckerNames * list. Returns that expansion, or the argument itself if the expansion fails. */ private static String unshorthandProcessorName( final String processor, List fullyQualifiedCheckerNames, boolean allowSubcheckers) { for (final String name : fullyQualifiedCheckerNames) { boolean tryMatch = false; String[] checkerPath = name.substring(0, name.length() - "Checker".length()).split("\\."); String checkerNameShort = checkerPath[checkerPath.length - 1]; String checkerName = checkerNameShort + "Checker"; if (name.endsWith("Checker")) { checkerPath = name.substring(0, name.length() - "Checker".length()).split("\\."); checkerNameShort = checkerPath[checkerPath.length - 1]; checkerName = checkerNameShort + "Checker"; tryMatch = true; } else if (allowSubcheckers && name.endsWith("Subchecker")) { checkerPath = name.substring(0, name.length() - "Subchecker".length()).split("\\."); checkerNameShort = checkerPath[checkerPath.length - 1]; checkerName = checkerNameShort + "Subchecker"; tryMatch = true; } if (tryMatch) { if (processor.equalsIgnoreCase(checkerName) || processor.equalsIgnoreCase(checkerNameShort)) { return name; } } } return processor; // If not matched, return the input string. } /** * Given a shorthand processor name, returns true if it can be expanded to a checker in the * fullyQualifiedCheckerNames list. Does not match the subtyping checker. * * @param processor a string identifying one processor * @param fullyQualifiedCheckerNames a list of fully-qualified checker names to match processor * against * @param allowSubcheckers whether to match against fully qualified checker names ending with * "Subchecker" */ public static boolean matchesFullyQualifiedProcessor( final String processor, List fullyQualifiedCheckerNames, boolean allowSubcheckers) { return !processor.equals( unshorthandProcessorName(processor, fullyQualifiedCheckerNames, allowSubcheckers)); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy