| package org.checkerframework.framework.source; |
| |
| import com.sun.source.util.TreePath; |
| import com.sun.tools.javac.processing.JavacProcessingEnvironment; |
| import com.sun.tools.javac.util.Context; |
| import com.sun.tools.javac.util.Log; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import javax.annotation.processing.ProcessingEnvironment; |
| import javax.lang.model.element.TypeElement; |
| import javax.tools.Diagnostic.Kind; |
| |
| /** |
| * An aggregate checker that packages multiple checkers together. The resulting checker invokes the |
| * component checkers in turn on the processed files. |
| * |
| * <p>There is no communication, interaction, or cooperation between the component checkers, even to |
| * the extent of being able to read one another's qualifiers. An aggregate checker is merely |
| * shorthand to invoke a sequence of checkers. |
| * |
| * <p>This class delegates {@code AbstractTypeProcessor} responsibilities to each component checker. |
| * |
| * <p>Checker writers need to subclass this class and only override {@link #getSupportedCheckers()} |
| * to indicate the classes of the checkers to be bundled. |
| */ |
| public abstract class AggregateChecker extends SourceChecker { |
| |
| protected final List<SourceChecker> checkers; |
| |
| /** |
| * Returns the list of supported checkers to be run together. Subclasses need to override this |
| * method. |
| */ |
| protected abstract Collection<Class<? extends SourceChecker>> getSupportedCheckers(); |
| |
| /** Create a new AggregateChecker. */ |
| protected AggregateChecker() { |
| Collection<Class<? extends SourceChecker>> checkerClasses = getSupportedCheckers(); |
| |
| checkers = new ArrayList<>(checkerClasses.size()); |
| for (Class<? extends SourceChecker> checkerClass : checkerClasses) { |
| try { |
| SourceChecker instance = checkerClass.getDeclaredConstructor().newInstance(); |
| instance.setParentChecker(this); |
| checkers.add(instance); |
| } catch (Exception e) { |
| message(Kind.ERROR, "Couldn't instantiate an instance of " + checkerClass); |
| } |
| } |
| } |
| |
| /** |
| * processingEnv needs to be set on each checker since we are not calling init on the checker, |
| * which leaves it null. If one of checkers is an AggregateChecker, its visitors will try use |
| * checker's processing env which should not be null. |
| */ |
| @Override |
| protected void setProcessingEnvironment(ProcessingEnvironment env) { |
| super.setProcessingEnvironment(env); |
| for (SourceChecker checker : checkers) { |
| checker.setProcessingEnvironment(env); |
| } |
| } |
| |
| @Override |
| public void initChecker() { |
| // No need to call super, it might result in reflective instantiations |
| // of visitor/factory classes. |
| // super.initChecker(); |
| // To prevent the warning that initChecker wasn't called. |
| messager = processingEnv.getMessager(); |
| |
| // first initialize all checkers |
| for (SourceChecker checker : checkers) { |
| checker.initChecker(); |
| } |
| // then share options as necessary |
| for (SourceChecker checker : checkers) { |
| // We need to add all options that are activated for the aggregate to |
| // the individual checkers. |
| checker.addOptions(super.getOptions()); |
| // Each checker should "support" all possible lint options - otherwise |
| // subchecker A would complain about a lint option for subchecker B. |
| checker.setSupportedLintOptions(this.getSupportedLintOptions()); |
| } |
| allCheckersInited = true; |
| } |
| |
| // Whether all checkers were successfully initialized. |
| private boolean allCheckersInited = false; |
| |
| // AbstractTypeProcessor delegation |
| @Override |
| public final void typeProcess(TypeElement element, TreePath tree) { |
| Context context = ((JavacProcessingEnvironment) processingEnv).getContext(); |
| Log log = Log.instance(context); |
| if (log.nerrors > this.errsOnLastExit) { |
| // If there is a Java error, do not perform any of the component type checks, but come back |
| // for the next compilation unit. |
| this.errsOnLastExit = log.nerrors; |
| return; |
| } |
| if (!allCheckersInited) { |
| // If there was an initialization problem, an |
| // error was already output. Just quit. |
| return; |
| } |
| for (SourceChecker checker : checkers) { |
| checker.errsOnLastExit = this.errsOnLastExit; |
| checker.typeProcess(element, tree); |
| if (checker.javacErrored) { |
| this.javacErrored = true; |
| return; |
| } |
| this.errsOnLastExit = checker.errsOnLastExit; |
| } |
| } |
| |
| @Override |
| public void typeProcessingOver() { |
| for (SourceChecker checker : checkers) { |
| checker.typeProcessingOver(); |
| } |
| super.typeProcessingOver(); |
| } |
| |
| @Override |
| public final Set<String> getSupportedOptions() { |
| Set<String> options = new HashSet<>(); |
| for (SourceChecker checker : checkers) { |
| options.addAll(checker.getSupportedOptions()); |
| } |
| options.addAll(expandCFOptions(Arrays.asList(this.getClass()), options.toArray(new String[0]))); |
| return options; |
| } |
| |
| @Override |
| public final Map<String, String> getOptions() { |
| Map<String, String> options = new HashMap<>(super.getOptions()); |
| for (SourceChecker checker : checkers) { |
| options.putAll(checker.getOptions()); |
| } |
| return options; |
| } |
| |
| @Override |
| public final Set<String> getSupportedLintOptions() { |
| Set<String> lints = new HashSet<>(); |
| for (SourceChecker checker : checkers) { |
| lints.addAll(checker.getSupportedLintOptions()); |
| } |
| return lints; |
| } |
| |
| @Override |
| protected SourceVisitor<?, ?> createSourceVisitor() { |
| return new SourceVisitor<Void, Void>(this) { |
| // Aggregate checkers do not visit source, |
| // the checkers in the aggregate checker do. |
| }; |
| } |
| |
| // TODO some methods in a component checker should behave differently if they |
| // are part of an aggregate, e.g. getSuppressWarningKeys should additionally |
| // return the name of the aggregate checker. |
| // We could add a query method in SourceChecker that refers to the aggregate, if present. |
| // At the moment, all the component checkers manually need to add the name of the aggregate. |
| } |