blob: d8a82f41c41e18c05534d474969fdcb44bdcb047 [file] [log] [blame]
/*
* Copyright 2008 Google Inc.
*
* Licensed 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 com.google.gwt.user.rebind.rpc;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.typeinfo.JClassType;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
* A collection of reported problems; these are accumulated during the
* SerializableTypeOracleBuilder's isSerializable analysis, and what to do about
* the problems is decided only later.
*/
public class ProblemReport {
/**
* Priority of problems. {@link #FATAL} problems will fail a build that
* would otherwise have succeeded, for example because of a bad custom
* serializer used only as a subclass of a superclass with other viable
* subtypes. {@link #DEFAULT} problems might or might not be fatal,
* depending on overall results accumulated later. {@link #AUXILIARY}
* problems are not fatal, and often not even problems by themselves, but
* diagnostics related to default problems (e.g. type filtration, which
* might suppress an intended-to-serialize class).
*/
public enum Priority { FATAL, DEFAULT, AUXILIARY}
/**
* An individual report, which may require multiple entries (expressed as
* logs under a branchpoint), but relates to an individual issue.
*/
public static class Problem {
private String message;
private List<String> childMessages;
private Problem(String message, String[] children) {
this.message = message;
// most problems don't have sub-messages, so init at zero size
childMessages = new ArrayList<String>(children.length);
for (int i = 0; i < children.length; i++) {
childMessages.add(children[i]);
}
}
public void addChild(String message) {
childMessages.add(message);
}
public String getPrimaryMessage() {
return message;
}
public Iterable<String> getSubMessages() {
return childMessages;
}
public boolean hasSubMessages() {
return !childMessages.isEmpty();
}
}
private Map<JClassType, List<Problem>> allProblems;
private Map<JClassType, List<Problem>> auxiliaries;
private Map<JClassType, List<Problem>> fatalProblems;
private JClassType contextType;
/**
* Creates a new, empty, context-less ProblemReport.
*/
public ProblemReport() {
Comparator<JClassType> comparator = new Comparator<JClassType>() {
public int compare(JClassType o1, JClassType o2) {
assert o1 != null;
assert o2 != null;
return o1.getParameterizedQualifiedSourceName().compareTo(
o2.getParameterizedQualifiedSourceName());
}
};
allProblems = new TreeMap<JClassType, List<Problem>>(comparator);
auxiliaries = new TreeMap<JClassType, List<Problem>>(comparator);
fatalProblems = new TreeMap<JClassType, List<Problem>>(comparator);
contextType = null;
}
/**
* Adds a problem for a given type. This also sorts the problems into
* collections by priority.
*
* @param type the problematic type
* @param message the description of the problem
* @param priority priority of the problem.
* @param extraLines additional continuation lines for the message, usually
* for additional explanations.
*/
public Problem add(JClassType type, String message, Priority priority,
String... extraLines) {
String contextString = "";
if (contextType != null) {
contextString = " (reached via " +
contextType.getParameterizedQualifiedSourceName() + ")";
}
message = message + contextString;
Problem entry = new Problem(message, extraLines);
if (priority == Priority.AUXILIARY) {
addToMap(type, entry, auxiliaries);
return entry;
}
// both FATAL and DEFAULT problems go in allProblems...
addToMap(type, entry, allProblems);
// only FATAL problems go in fatalProblems...
if (priority == Priority.FATAL) {
addToMap(type, entry, fatalProblems);
}
return entry;
}
public String getWorstMessageForType(JClassType type) {
List<Problem> list = fatalProblems.get(type);
if (list == null) {
list = allProblems.get(type);
if (list == null) {
list = auxiliaries.get(type);
}
}
if (list == null) {
return null;
}
return list.get(0).getPrimaryMessage() + (list.size() > 1 ? ", etc." : "");
}
/**
* Were any problems reported as "fatal"?
*/
public boolean hasFatalProblems() {
return !fatalProblems.isEmpty();
}
/**
* Reports all problems to the logger supplied, at the log level supplied.
* The problems are assured of being reported in lexographic order of
* type names.
*
* @param logger logger to receive problem reports
* @param problemLevel severity level at which to report problems.
* @param auxLevel severity level at which to report any auxiliary messages.
*/
public void report(TreeLogger logger, TreeLogger.Type problemLevel,
TreeLogger.Type auxLevel) {
doReport(logger, auxLevel, auxiliaries);
doReport(logger, problemLevel, allProblems);
}
/**
* Reports only urgent problems to the logger supplied, at the log level
* supplied. The problems are assured of being reported in lexographic
* order of type names.
*
* @param logger logger to receive problem reports
* @param level severity level at which to report problems.
*/
public void reportFatalProblems(TreeLogger logger, TreeLogger.Type level) {
doReport(logger, level, fatalProblems);
}
/**
* Sets the context type currently being analyzed. Problems found will
* include reference to this context, until reset with another call to this
* method. Context may be cancelled with a {@code null} value here.
*
* @param newContext the type under analysis
*/
public void setContextType(JClassType newContext) {
contextType = newContext;
}
/**
* Test accessor returning list of auxiliary "problems" logged against a
* given type.
*
* @param type type to fetch problems for
* @return {@code null} if no auxiliaries were logged. Otherwise, a list
* of strings describing messages, including the context in which the
* problem was found.
*/
List<Problem> getAuxiliaryMessagesForType(JClassType type) {
List<Problem> list = auxiliaries.get(type);
if (list == null) {
list = new ArrayList<Problem>(0);
}
return list;
}
/**
* Test accessor returning list of problems logged against a given type.
*
* @param type type to fetch problems for
* @return {@code null} if no problems were logged. Otherwise, a list
* of strings describing problems, including the context in which the
* problem was found.
*/
List<Problem> getProblemsForType(JClassType type) {
List<Problem> list = allProblems.get(type);
if (list == null) {
list = new ArrayList<Problem>(0);
}
return list;
}
/**
* Adds an entry to one of the problem maps.
*
* @param type the type to add
* @param message the message to add for {@code type}
* @param map the map to add to
*/
private void addToMap(JClassType type, Problem problem,
Map<JClassType, List<Problem>> map) {
List<Problem> list = map.get(type);
if (list == null) {
list = new ArrayList<Problem>();
map.put(type, list);
}
list.add(problem);
}
/**
* Logs all of the problems from one of the problem maps.
*
* @param logger the logger to log to
* @param level the level for messages
* @param problems the problems to log
*/
private void doReport(TreeLogger logger, Type level,
Map<JClassType, List<Problem>> problems) {
for (List<Problem> problemList : problems.values()) {
for (Problem problem : problemList) {
if (problem.hasSubMessages()) {
TreeLogger sublogger = logger.branch(level, problem.getPrimaryMessage());
for (String sub : problem.getSubMessages()) {
sublogger.log(level, sub);
}
} else {
logger.log(level, problem.getPrimaryMessage());
}
}
}
}
}