/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* Part of the Processing project - http://processing.org Copyright (c) 2012-15 The Processing Foundation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package processing.mode.java.pdex; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.internal.compiler.problem.DefaultProblem; import processing.app.Language; import processing.app.Messages; import processing.core.PApplet; import processing.data.StringList; public class ErrorMessageSimplifier { /** * Mapping between ProblemID constant and the constant name. Holds about 650 * of them. Also, this is just temporary, will be used to find the common * error types, cos you know, identifying String names is easier than * identifying 8 digit int constants! * TODO: this is temporary */ private static TreeMap<Integer, String> constantsMap; private static final boolean DEBUG = false; private static final Pattern tokenRegExp = Pattern.compile("\\b token\\b"); private static void prepareConstantsList() { constantsMap = new TreeMap<>(); Class<DefaultProblem> probClass = DefaultProblem.class; Field f[] = probClass.getFields(); for (Field field : f) { if (Modifier.isStatic(field.getModifiers())) try { if (DEBUG) { Messages.log(field.getName() + " :" + field.get(null)); } Object val = field.get(null); if (val instanceof Integer) { constantsMap.put((Integer) (val), field.getName()); } } catch (Exception e) { e.printStackTrace(); break; } } if (DEBUG) { Messages.log("Total items: " + constantsMap.size()); } } public static String getIDName(int id) { if (constantsMap == null) { prepareConstantsList(); } return constantsMap.get(id); } /** * Tones down the jargon in the ecj reported errors. */ public static String getSimplifiedErrorMessage(IProblem iprob) { if (iprob == null) return null; String args[] = iprob.getArguments(); if (DEBUG) { Messages.log("Simplifying message: " + iprob.getMessage() + " ID: " + getIDName(iprob.getID())); Messages.log("Arg count: " + args.length); for (String arg : args) { Messages.log("Arg " + arg); } } String result = null; switch (iprob.getID()) { case IProblem.ParsingError: if (args.length > 0) { result = Language.interpolate("editor.status.error_on", args[0]); } break; case IProblem.ParsingErrorDeleteToken: if (args.length > 0) { result = Language.interpolate("editor.status.error_on", args[0]); } break; case IProblem.ParsingErrorInsertToComplete: if (args.length > 0) { if (args[0].length() == 1) { result = getErrorMessageForBracket(args[0].charAt(0)); } else { if (args[0].equals("AssignmentOperator Expression")) { result = Language.interpolate("editor.status.missing.add", "="); } else if (args[0].equalsIgnoreCase(") Statement")) { result = getErrorMessageForBracket(args[0].charAt(0)); } else { result = Language.interpolate("editor.status.error_on", args[0]); } } } break; case IProblem.ParsingErrorInvalidToken: if (args.length > 0) { if (args[1].equals("VariableDeclaratorId")) { if (args[0].equals("int")) { result = Language.text("editor.status.reserved_words"); } else { result = Language.interpolate("editor.status.error_on", args[0]); } } else { result = Language.interpolate("editor.status.error_on", args[0]); } } break; case IProblem.ParsingErrorInsertTokenAfter: if (args.length > 0) { if (args[1].length() == 1) { result = getErrorMessageForBracket(args[1].charAt(0)); } else { // https://github.com/processing/processing/issues/3104 if (args[1].equalsIgnoreCase("Statement")) { result = Language.interpolate("editor.status.error_on", args[0]); } else { result = Language.interpolate("editor.status.error_on", args[0]) + " " + Language.interpolate("editor.status.missing.add", args[1]); } } } break; case IProblem.UndefinedConstructor: if (args.length == 2) { String constructorName = args[0]; // For messages such as "contructor sketch_name.ClassXYZ() is undefined", change // constructor name to "ClassXYZ()". See #3434 if (constructorName.contains(".")) { // arg[0] contains sketch name twice: sketch_150705a.sketch_150705a.Thing constructorName = constructorName.substring(constructorName.indexOf('.') + 1); constructorName = constructorName.substring(constructorName.indexOf('.') + 1); } String constructorArgs = removePackagePrefixes(args[args.length - 1]); result = Language.interpolate("editor.status.undefined_constructor", constructorName, constructorArgs); } break; case IProblem.UndefinedMethod: if (args.length > 2) { String methodName = args[args.length - 2]; String methodArgs = removePackagePrefixes(args[args.length - 1]); result = Language.interpolate("editor.status.undefined_method", methodName, methodArgs); } break; case IProblem.ParameterMismatch: if (args.length > 3) { // 2nd arg is method name, 3rd arg is correct param list if (args[2].trim().length() == 0) { // the case where no params are needed. result = Language.interpolate("editor.status.empty_param", args[1]); } else { result = Language.interpolate("editor.status.wrong_param", args[1], args[1], removePackagePrefixes(args[2])); // String method = q(args[1]); // String methodDef = " \"" + args[1] + "(" + getSimpleName(args[2]) + ")\""; // result = result.replace("method", method); // result += methodDef; } } break; case IProblem.UndefinedField: if (args.length > 0) { result = Language.interpolate("editor.status.undef_global_var", args[0]); } break; case IProblem.UndefinedType: if (args.length > 0) { result = Language.interpolate("editor.status.undef_class", args[0]); } break; case IProblem.UnresolvedVariable: if (args.length > 0) { result = Language.interpolate("editor.status.undef_var", args[0]); } break; case IProblem.UndefinedName: if (args.length > 0) { result = Language.interpolate("editor.status.undef_name", args[0]); } break; case IProblem.TypeMismatch: if (args.length > 1) { result = Language.interpolate("editor.status.type_mismatch", args[0], args[1]); // result = result.replace("typeA", q(args[0])); // result = result.replace("typeB", q(args[1])); } break; case IProblem.LocalVariableIsNeverUsed: if (args.length > 0) { result = Language.interpolate("editor.status.unused_variable", args[0]); } break; case IProblem.UninitializedLocalVariable: if (args.length > 0) { result = Language.interpolate("editor.status.uninitialized_variable", args[0]); } break; case IProblem.AssignmentHasNoEffect: if (args.length > 0) { result = Language.interpolate("editor.status.no_effect_assignment", args[0]); } break; case IProblem.HidingEnclosingType: if (args.length > 0) { result = Language.interpolate("editor.status.hiding_enclosing_type", args[0]); } break; default: String message = iprob.getMessage(); if (message != null) { // Remove all instances of token // "Syntax error on token 'blah', delete this token" Matcher matcher = tokenRegExp.matcher(message); message = matcher.replaceAll(""); result = message; } } if (DEBUG) { Messages.log("Simplified Error Msg: " + result); } return result; } /** * Converts java.lang.String into String, etc */ static private String removePackagePrefixes(String input) { if (!input.contains(".")) { return input; } String[] names = PApplet.split(input, ','); // List<String> names = new ArrayList<String>(); // if (inp.indexOf(',') >= 0) { // names.addAll(Arrays.asList(inp.split(","))); // } else { // names.add(inp); // } StringList result = new StringList(); for (String name : names) { int dot = name.lastIndexOf('.'); if (dot >= 0) { name = name.substring(dot + 1, name.length()); } result.append(name); } return result.join(", "); } static private String getErrorMessageForBracket(char c) { switch (c) { case ';': return Language.text("editor.status.missing.semicolon"); case '[': return Language.text("editor.status.missing.left_sq_bracket"); case ']': return Language.text("editor.status.missing.right_sq_bracket"); case '(': return Language.text("editor.status.missing.left_paren"); case ')': return Language.text("editor.status.missing.right_paren"); case '{': return Language.text("editor.status.missing.left_curly_bracket"); case '}': return Language.text("editor.status.missing.right_curly_bracket"); } // This seems to be unreachable and wasn't in PDE.properties. // I've added it for 3.0a8, but that seems gross. [fry] return Language.interpolate("editor.status.missing.default", c); } // static private final String q(Object quotable) { // return "\"" + quotable + "\""; // } // static private final String qs(Object quotable) { // return " " + q(quotable); // } }