package org.checkerframework.eclipse.javac; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; import org.checkerframework.eclipse.CheckerPlugin; import org.checkerframework.eclipse.error.CheckerErrorStatus; import org.checkerframework.eclipse.prefs.CheckerPreferences; import org.checkerframework.eclipse.util.Util; import org.eclipse.ui.statushandlers.StatusManager; /** Error reported by javac. Created by parsing javac output. */ public class JavacError { private static final boolean VERBOSE = true; public final File file; public final int lineNumber; public final String errorKey; public final List<String> errorArguments; public final String message; public final Diagnostic<? extends JavaFileObject> diag; public final int startPosition; public final int endPosition; public JavacError( File file, int lineNumber, String errorKey, List<String> errorArguments, String message, int startPosition, int endPosition, Diagnostic<? extends JavaFileObject> diag) { this.file = file; this.lineNumber = lineNumber; this.message = message; this.startPosition = startPosition; this.endPosition = endPosition; this.errorKey = errorKey; this.errorArguments = errorArguments; this.diag = diag; } public JavacError(Diagnostic<? extends JavaFileObject> diag) { this( new File(diag.getSource().toUri().getPath()), (int) diag.getLineNumber(), null, null, diag.getMessage(null), (int) diag.getStartPosition(), (int) diag.getEndPosition(), diag); } public JavacError(File file, int lineNumber, String message) { this(file, lineNumber, null, null, message, -1, -1, null); } public JavacError( File file, int lineNumber, String errorKey, List<String> errorArguments, String message, int startPosition, int endPosition) { this(file, lineNumber, errorKey, errorArguments, message, startPosition, endPosition, null); } @Override public String toString() { return file.getPath() + ":" + lineNumber + ": " + message; } /** Parses javac output and converts to a list of errors. */ private static final Pattern errorCountPattern = Pattern.compile("^[0-9]+ (error|warning)*s?$"); private static final Pattern noncheckerPattern = Pattern.compile("^(Note|warning|error): .* $"); private static final String headMessagePatternString = "^(.*):(\\d*): (?:(?:warning|error)?: ?)?\\((.*)\\) \\$\\$ (\\d*) "; private static final Pattern headMessagePattern = Pattern.compile(headMessagePatternString + ".*"); private static final Pattern trimmingPattern = Pattern.compile(headMessagePatternString + "(.*)"); private static final String argumentMessagePatternString = "\\$\\$ (.*) "; private static final String tailMessagePatternString = "\\$\\$ (?:(?:\\( (-?\\d+), (-?\\d+) \\))|null) \\$\\$ (.*)$"; private static final Pattern noProcessorPattern = Pattern.compile("^error: Annotation processor (.*) not found$"); private static final Pattern invalidFlagPattern = Pattern.compile("^javac: invalid flag: (.*)$"); private static final Pattern missingFilePattern = Pattern.compile("^error: Could not find class file for (.*)\\.$"); private static Pattern createCompletePattern(int numberOfArguments) { StringBuilder sb = new StringBuilder(); sb.append(headMessagePatternString); for (int i = 0; i < numberOfArguments; ++i) { sb.append(argumentMessagePatternString); } sb.append(tailMessagePatternString); return Pattern.compile(sb.toString()); } /** * If errorStr matches the expected pattern of an error report this method will return the part * of the errorStr WITHOUT the adetailedmsg information. Otherwise errorStr is just returned. */ public static String trimDetails(final String errorStr) { final Matcher matcher = trimmingPattern.matcher(errorStr); if (matcher.matches()) { int shave = matcher.group(4).length() + matcher.group(5).length() + " $$ ".length(); return errorStr.substring(0, errorStr.length() - shave); } return errorStr; } public static List<JavacError> parse(String javacoutput) { if (VERBOSE) System.out.println("javac output:\n" + javacoutput); if (javacoutput == null) return null; List<JavacError> result = new ArrayList<JavacError>(); List<String> lines = Arrays.asList(javacoutput.split(Util.NL)); if (!handleErrors(lines.get(0))) { return result; } File errorFile = null; int lineNum = 0; StringBuilder messageBuilder = new StringBuilder(); Iterator<String> iter = lines.iterator(); String errorKey = null; int startPosition = -1; int endPosition = -1; int numberOfArguments = -1; List<String> errorArguments = null; while (iter.hasNext()) { String line = iter.next(); Matcher matcher = headMessagePattern.matcher(line.trim()); if (matcher.matches()) { if (errorFile != null) { JavacError error = new JavacError( errorFile, lineNum, errorKey, errorArguments, messageBuilder.toString().trim(), startPosition, endPosition); result.add(error); } errorFile = new File(matcher.group(1)); lineNum = Integer.parseInt(matcher.group(2)); errorKey = matcher.group(3); numberOfArguments = Integer.parseInt(matcher.group(4)); Matcher completeMatcher = createCompletePattern(numberOfArguments).matcher(line.trim()); errorArguments = new ArrayList<String>(); messageBuilder = new StringBuilder(); if (completeMatcher.matches()) { for (int i = 0; i < numberOfArguments; ++i) { errorArguments.add(completeMatcher.group(5 + i)); } startPosition = Integer.parseInt(completeMatcher.group(5 + numberOfArguments)); endPosition = Integer.parseInt(completeMatcher.group(6 + numberOfArguments)); if (endPosition < startPosition) { endPosition = startPosition; } messageBuilder.append(completeMatcher.group(7 + numberOfArguments)); } messageBuilder.append(Util.NL); } else { if (errorCountPattern.matcher(line).matches() || !iter.hasNext()) { if (messageBuilder.length() != 0) { JavacError error = new JavacError( errorFile, lineNum, errorKey, errorArguments, messageBuilder.toString().trim(), startPosition, endPosition); result.add(error); } } else if (!line.trim().equals("^") && !noncheckerPattern.matcher(line).matches()) { messageBuilder.append(line); messageBuilder.append(Util.NL); } } } // filter out for errors/warnings matching a regex String filterRegex = CheckerPlugin.getDefault() .getPreferenceStore() .getString(CheckerPreferences.PREF_CHECKER_ERROR_FILTER_REGEX); if (!filterRegex.isEmpty()) { Iterator<JavacError> errorIter = result.iterator(); while (errorIter.hasNext()) { JavacError err = errorIter.next(); Matcher filterMatcher = Pattern.compile(filterRegex, Pattern.DOTALL).matcher(err.message); if (filterMatcher.matches()) { errorIter.remove(); } } } return result; } /** * Handle special error output cases for the compiler. * * @return true if no errors are present, false otherwise */ private static boolean handleErrors(String line) { StatusManager manager = StatusManager.getManager(); // special case for missing checkers.jar (or processor class) Matcher procMatcher = noProcessorPattern.matcher(line); if (procMatcher.matches()) { CheckerErrorStatus status; if (procMatcher.group(1).equals("''")) { status = new CheckerErrorStatus( "No checkers configured. Use the plugin preferences to configure checkers to use."); } else { status = new CheckerErrorStatus( "Annotation processor " + procMatcher.group(1) + " could not be found. Try adding checkers.jar to your project build path."); } manager.handle(status, StatusManager.SHOW); return false; } // Misc errors that prevent compiler from running: Matcher flagMatcher = invalidFlagPattern.matcher(line); if (flagMatcher.matches()) { manager.handle( new CheckerErrorStatus( "Invalid compiler flag: " + flagMatcher.group(1) + ". Check your preferences for invalid flags."), StatusManager.SHOW); return false; } Matcher missingFileMatcher = missingFilePattern.matcher(line); if (missingFileMatcher.matches()) { manager.handle( new CheckerErrorStatus( "Cannot find file: " + missingFileMatcher.group(1) + ". You may have malformed input in your preferences."), StatusManager.SHOW); return false; } return true; } }