/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.flex.compiler.clients.problems; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.apache.flex.compiler.config.ICompilerProblemSettings; import org.apache.flex.compiler.problems.AbstractSemanticProblem; import org.apache.flex.compiler.problems.CodegenInternalProblem; import org.apache.flex.compiler.problems.CodegenProblem; import org.apache.flex.compiler.problems.CompilerProblemSeverity; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.compiler.problems.ParserProblem; import org.apache.flex.compiler.problems.SemanticWarningProblem; import org.apache.flex.compiler.problems.StrictSemanticsProblem; import org.apache.flex.compiler.problems.UnfoundPropertyProblem; import org.apache.flex.compiler.problems.collections.CompositeProblemFilter; import org.apache.flex.compiler.problems.collections.FilteredIterator; import com.google.common.collect.Iterables; /** * A ProblemQuery presents a higher-level view of a problem * stream; it offers facilities to enable or disable diagnostics * and presents filtered and (optionally) sorted views of the * underlying problem stream. */ public class ProblemQuery { /** * Construct a ProblemQuery. */ public ProblemQuery() { this(null); } /** * Construct a ProblemQuery with settings to control how compiler problems * are reported. * * @param problemSettings configuration settings to control how compiler * problems are reported. */ public ProblemQuery(ICompilerProblemSettings problemSettings) { this.problems = new ArrayList<ICompilerProblem>(); // An empty problem set is sorted. this.sorted = true; // Start with no restrictions on the problems presented. this.problemFilter = new ProblemFilterClassCriteria(); this.problemSettings = problemSettings; } /** The underlying problem collection. */ private ArrayList<ICompilerProblem> problems; /** * Set to true when the problem collection * is known to be in sorted order. */ private boolean sorted; /** * Problems being rejected as a class. */ private ProblemFilterClassCriteria problemFilter; /** * Configuration settings that control how errors are * reported. */ private ICompilerProblemSettings problemSettings; /** * Gets the list of compiler problems, with no filtering or sorting. */ public List<ICompilerProblem> getProblems() { return problems; } /** * Enable or disable strict semantics mode diagnostics. * @param isStrict - if true, strict semantics mode * diagnostics will appear in the filtered diagnostics. */ public void setShowStrictSemantics(boolean isStrict) { setShowProblemByClass(StrictSemanticsProblem.class, isStrict); } /** * Enable or disable semantic warnings. * @param showWarnings - if true, semantic warnings * will appear in the filtered diagnostics. */ public void setShowWarnings(boolean showWarnings) { setShowProblemByClass(SemanticWarningProblem.class, showWarnings); } /** * Enable or disable internal error display. * @param showInternalErrors - if true, internal error * diagnostics will appear in the filtered diagnostics. */ public void setShowInternalErrors(boolean showInternalErrors) { setShowProblemByClass(CodegenInternalProblem.class,showInternalErrors); } /** * Enable or disable display of "problems" that * are used for compiler internal processing but * have no relevance to the user. * @param showIrrelevantProblems - if true, non-user-relevant * diagnostics will appear in the filtered diagnostics. */ public void setShowIrrelevantProblems(boolean showIrrelevantProblems) { setShowProblemByClass(UnfoundPropertyProblem.class,showIrrelevantProblems); } /** * Enable or disable display of a specific problem class or superclass. * @param problemClass - the problem class/superclass of interest. * @param enable - if true, instances of this problem class * will appear in the filtered diagnostics. */ public void setShowProblemByClass(Class<? extends ICompilerProblem> problemClass, final boolean enable) { if ( enable ) { problemFilter.removeRejectedClass(problemClass); } else { problemFilter.addRejectedClass(problemClass); } } /** * Add an array of compiler problems to the problems collection. * @param newProblems - the problems to add. * @post the problems collection is marked "not sorted." */ public void addAll(ICompilerProblem[] newProblems) { addAll(Arrays.asList(newProblems)); } /** * Add a collection of compiler problems to the problems collection. * @param newProblems - the problems to add. * @post the problems collection is marked "not sorted." */ public void addAll(Iterable<ICompilerProblem> newProblems) { this.sorted = false; Iterables.addAll(this.problems, newProblems); } /** * Add a single problem to the problems collection. * @param problem - the problem to add. * @post the problems collection is marked "not sorted." */ public void add(ICompilerProblem problem) { this.sorted = false; this.problems.add(problem); } /** * Clear the underlying collection of problems. */ public void clear() { this.sorted = true; this.problems.clear(); } /** * Get an iterator over the set of problems that * are to be reported based on the current settings. * Problem categorization is built into this filter so that problems * categorized as "ignore" are filtered out. * * @return an Iterable<ICompilerProblem> over the * subset of problems that are of interest to the client. */ public Iterable<ICompilerProblem> getFilteredProblems() { IProblemFilter filter = CompositeProblemFilter.and(this.problemFilter, new SkipSemanticCascadesFilter()); CompilerProblemCategorizer categorizer = new CompilerProblemCategorizer(problemSettings); CodeGenErrorFilter cgef = new CodeGenErrorFilter(); if (cgef.hasOtherErrors(problems)) { filter = CompositeProblemFilter.and(filter, cgef); } return getFilteredProblems(CompositeProblemFilter.and(filter, new ErrorsAndWarningsFilter(categorizer))); } /** * Used internally as a base filter without any categorization built in. * This is useful for clients that want to do the categorization themselves. * Gets an iterator over the set of problems that are to be reported based * on the current settings. * * @return an Iterable<ICompilerProblem> over the subset of problems * that are of interest to the client. */ private Iterable<ICompilerProblem> getFilteredProblemsUsingBaseFilter() { return getFilteredProblems(CompositeProblemFilter.and(this.problemFilter, new SkipSemanticCascadesFilter())); } /** * Get an iterator over the set of problems that * are to be reported based on the current settings. * * @param filter - the filter to apply. * * @return an Iterable<ICompilerProblem> over the * subset of problems that are of interest to the client. */ private Iterable<ICompilerProblem> getFilteredProblems(IProblemFilter filter) { if (problemSettings != null) filter = CompositeProblemFilter.and(filter, new ProblemSettingsFilter(problemSettings)); // Sort the problems so that the semantic cascade // filter can find semantic problems that occur // on the same line as parser problems. sortProblems(); return getProblemView(filter); } /** * Any problems to report after filtering? * @return true if there are filtered problems. */ public boolean hasFilteredProblems() { return getFilteredProblems().iterator().hasNext(); } /** * Do any problems match the given filter? * @param filter - the problem filter of interest. */ public boolean hasFilteredProblems(final IProblemFilter filter) { return getProblemView(filter).iterator().hasNext(); } /** * Get a filtered view of the underlying problems. * @param filter - the filter to apply. * @return an Iterable that supplies a filtered iterator of the problems. */ public Iterable<ICompilerProblem> getProblemView(final IProblemFilter filter) { return FilteredIterator.getFilteredIterable(problems, filter); } /** * Get an iterator over all internal errors. */ public Iterable<ICompilerProblem> getInternalErrors() { return getProblemView(new ProblemFilterClassCriteria(CodegenInternalProblem.class)); } /** * Sort the captured collection of problems so that we can filter out * semantic problems that occur on the same line as parser problems. */ public void sortProblems() { if ( ! this.sorted ) { Collections.sort( this.problems, compareByPositionAndPhase); } this.sorted = true; } /** * Categorize the compiler problems into two bins, errors and warning. * * @param errors the collection where the errors are added. * @param warnings the collection where the warnings are added. */ public void getErrorsAndWarnings(Collection<ICompilerProblem> errors, Collection<ICompilerProblem> warnings) { CompilerProblemCategorizer categorizer = new CompilerProblemCategorizer(problemSettings); // Get the filtered problems and classify the problems as either errors or // warnings. for (ICompilerProblem problem : getFilteredProblemsUsingBaseFilter()) { CompilerProblemSeverity severity = categorizer.getProblemSeverity(problem); if (severity == CompilerProblemSeverity.ERROR) errors.add(problem); else if (severity == CompilerProblemSeverity.WARNING) warnings.add(problem); } } /** * Test if any of the problems are errors. * * return true if any of the problems are errors, false otherwise. */ public boolean hasErrors() { CompilerProblemCategorizer categorizer = new CompilerProblemCategorizer(problemSettings); for (ICompilerProblem problem : getFilteredProblemsUsingBaseFilter()) { CompilerProblemSeverity severity = categorizer.getProblemSeverity(problem); if (severity == CompilerProblemSeverity.ERROR) return true; } return false; } /** * This Comparator compares problems based on three criteria: * <li> file path * <li> line number * <li> problem class - ParserProblems are "less than" semantic problems. */ public static final Comparator<ICompilerProblem> compareByPositionAndPhase = new Comparator<ICompilerProblem>() { @Override public int compare(ICompilerProblem p1, ICompilerProblem p2) { int result = compareStrings( p1.getSourcePath(), p2.getSourcePath()); if ( result == 0 ) result = p1.getLine() - p2.getLine(); if ( result == 0 ) result = compareProblemClasses(p1, p2); return result; } /** * Look for configurations of problem classes that * imply a definite arrangement; problems from the * syntax analysis phase get ordered before problems * from the semantic analysis phase so that the latter * can be elided. */ private int compareProblemClasses(ICompilerProblem p1, ICompilerProblem p2) { if ( p1 instanceof ParserProblem ) { if ( !(p2 instanceof ParserProblem) ) return -1; } else if ( p2 instanceof ParserProblem ) { if ( !(p1 instanceof ParserProblem) ) return 1; } return 0; } }; /** * SkipSemanticCascadesFilter accepts any problem except a * SemanticProblem or CodegenProblem on a line that has already * reported some kind of parser problem. */ private class SkipSemanticCascadesFilter implements IProblemFilter { private int lineNumber = -1; private String fileName = null; private boolean parserProblemOnLine = false; @Override public boolean accept(ICompilerProblem problem) { if ( problem.getLine() != lineNumber || compareStrings(this.fileName, problem.getSourcePath()) != 0 ) { this.lineNumber = problem.getLine(); this.fileName = problem.getSourcePath(); } else if ( parserProblemOnLine && (problem instanceof AbstractSemanticProblem || problem instanceof CodegenProblem) ) { // Skip this problem. return false; } this.parserProblemOnLine = problem instanceof ParserProblem; return true; } } /** * ErrorsAndWarningsFilter accepts any problem that has a severity * of error or warning. */ private class ErrorsAndWarningsFilter implements IProblemFilter { ErrorsAndWarningsFilter(CompilerProblemCategorizer categorizer) { this.categorizer = categorizer; } private final CompilerProblemCategorizer categorizer; @Override public boolean accept(ICompilerProblem problem) { CompilerProblemSeverity severity = categorizer.getProblemSeverity(problem); if (severity == CompilerProblemSeverity.ERROR || severity == CompilerProblemSeverity.WARNING) { return true; } return false; } } /** * Compare two strings, either or both of which may be null. * @param s1 - the first string. * @param s2 - the second string. * @return -1 if s1 is less than s2, either in the string comparison * sense or because s1 is null and s2 is not; 0 if the strings * compare equal; 1 if s1 is greater than s1, by the reasoning above. */ private static int compareStrings(String s1, String s2) { if ( s1 == s2 ) return 0; else if ( s1 != null && s2 != null ) return s1.compareTo(s2); else // One or the other is null, but not both. return s1 == null? -1: 1; } }