| /* |
| * Copyright 2009 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.dev.javac; |
| |
| import com.google.gwt.core.client.impl.ArtificialRescue; |
| import com.google.gwt.dev.jdt.SafeASTVisitor; |
| import com.google.gwt.dev.jjs.InternalCompilerException; |
| import com.google.gwt.dev.util.Empty; |
| import com.google.gwt.dev.util.JsniRef; |
| import com.google.gwt.dev.util.collect.Lists; |
| |
| import org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.internal.compiler.ast.Annotation; |
| import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; |
| import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.Expression; |
| import org.eclipse.jdt.internal.compiler.ast.MemberValuePair; |
| import org.eclipse.jdt.internal.compiler.ast.SingleMemberAnnotation; |
| import org.eclipse.jdt.internal.compiler.ast.StringLiteral; |
| import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; |
| import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.BlockScope; |
| import org.eclipse.jdt.internal.compiler.lookup.ClassScope; |
| import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope; |
| import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.ProblemReasons; |
| import org.eclipse.jdt.internal.compiler.lookup.ProblemReferenceBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; |
| |
| import java.util.List; |
| |
| /** |
| * Checks the validity of ArtificialRescue annotations. |
| * |
| * <ul> |
| * <li>(1) The ArtificialRescue annotation is only used in generated code.</li> |
| * <li>(2) The className value names a type known to GWT (ignoring access rules) |
| * </li> |
| * <li>(3) The methods and fields of the type are known to GWT</li> |
| * </ul> |
| */ |
| public class ArtificialRescueChecker { |
| private class Visitor extends SafeASTVisitor { |
| |
| { |
| assert collectTypes || reportErrors : "No work to be done"; |
| } |
| |
| @Override |
| public void endVisit(TypeDeclaration memberTypeDeclaration, ClassScope scope) { |
| processType(memberTypeDeclaration); |
| } |
| |
| @Override |
| public void endVisit(TypeDeclaration typeDeclaration, |
| CompilationUnitScope scope) { |
| processType(typeDeclaration); |
| } |
| |
| @Override |
| public void endVisitValid(TypeDeclaration localTypeDeclaration, BlockScope scope) { |
| processType(localTypeDeclaration); |
| } |
| |
| private void processArtificialRescue(Annotation rescue) { |
| if (!allowArtificialRescue) { |
| // Goal (1) |
| GWTProblem.recordError(rescue, cud, onlyGeneratedCode(), null); |
| return; |
| } |
| |
| String className = null; |
| String[] methods = Empty.STRINGS; |
| String[] fields = Empty.STRINGS; |
| for (MemberValuePair pair : rescue.memberValuePairs()) { |
| String name = String.valueOf(pair.name); |
| if ("className".equals(name)) { |
| className = pair.value.constant.stringValue(); |
| } else if ("methods".equals(name)) { |
| methods = stringArrayFromValue(pair.value); |
| } else if ("fields".equals(name)) { |
| fields = stringArrayFromValue(pair.value); |
| } |
| } |
| |
| assert className != null; |
| |
| if (collectTypes) { |
| referencedTypes = Lists.add(referencedTypes, className); |
| } |
| |
| boolean isArray = false; |
| while (className.endsWith("[]")) { |
| className = className.substring(0, className.length() - 2); |
| if (collectTypes) { |
| referencedTypes = Lists.add(referencedTypes, className); |
| } |
| isArray = true; |
| } |
| |
| if (!reportErrors) { |
| // Nothing else to do |
| return; |
| } |
| |
| // Goal (2) |
| // Strip off any array-like extensions and just find base type |
| |
| // Fix JSNI primitive type names to something JDT will understand |
| if (isArray && className.length() == 1) { |
| switch (className.charAt(0)) { |
| case 'B': |
| className = "byte"; |
| break; |
| case 'C': |
| className = "char"; |
| break; |
| case 'D': |
| className = "double"; |
| break; |
| case 'F': |
| className = "float"; |
| break; |
| case 'I': |
| className = "int"; |
| break; |
| case 'J': |
| className = "long"; |
| break; |
| case 'S': |
| className = "short"; |
| break; |
| case 'Z': |
| className = "boolean"; |
| break; |
| } |
| } |
| |
| char[][] compoundName = CharOperation.splitOn('.', |
| className.toCharArray()); |
| TypeBinding typeBinding = cud.scope.getType(compoundName, |
| compoundName.length); |
| if (typeBinding == null) { |
| GWTProblem.recordError(rescue, cud, notFound(className), null); |
| } else if (typeBinding instanceof ProblemReferenceBinding) { |
| ProblemReferenceBinding problem = (ProblemReferenceBinding) typeBinding; |
| if (problem.problemId() == ProblemReasons.NotVisible) { |
| // Ignore |
| } else if (problem.problemId() == ProblemReasons.NotFound) { |
| GWTProblem.recordError(rescue, cud, notFound(className), null); |
| } else { |
| GWTProblem.recordError(rescue, cud, |
| unknownProblem(className, problem), null); |
| } |
| } else if (typeBinding instanceof BaseTypeBinding) { |
| // No methods or fields on primitive types (3) |
| if (methods.length > 0) { |
| GWTProblem.recordError(rescue, cud, noMethodsAllowed(), null); |
| } |
| |
| if (fields.length > 0) { |
| GWTProblem.recordError(rescue, cud, noFieldsAllowed(), null); |
| } |
| } else if (typeBinding instanceof ReferenceBinding) { |
| ReferenceBinding ref = (ReferenceBinding) typeBinding; |
| |
| if (isArray) { |
| // No methods or fields on array types (3) |
| if (methods.length > 0) { |
| GWTProblem.recordError(rescue, cud, noMethodsAllowed(), null); |
| } |
| |
| if (fields.length > 0) { |
| GWTProblem.recordError(rescue, cud, noFieldsAllowed(), null); |
| } |
| } else { |
| // Check methods on reference types (3) |
| for (String method : methods) { |
| if (method.contains("@")) { |
| GWTProblem.recordError(rescue, cud, nameAndTypesOnly(), null); |
| continue; |
| } |
| JsniRef jsni = JsniRef.parse("@foo::" + method); |
| if (jsni == null) { |
| GWTProblem.recordError(rescue, cud, badMethodSignature(method), |
| null); |
| continue; |
| } |
| |
| if (jsni.memberName().equals( |
| String.valueOf(ref.compoundName[ref.compoundName.length - 1]))) { |
| // Constructor |
| } else { |
| MethodBinding[] methodBindings = ref.getMethods(jsni.memberName().toCharArray()); |
| if (methodBindings == null || methodBindings.length == 0) { |
| GWTProblem.recordError(rescue, cud, noMethod(className, |
| jsni.memberName()), null); |
| continue; |
| } |
| } |
| } |
| |
| // Check fields on reference types (3) |
| for (String field : fields) { |
| if (ref.getField(field.toCharArray(), false) == null) { |
| GWTProblem.recordError(rescue, cud, unknownField(field), null); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Examine a TypeDeclaration for ArtificialRescue annotations. Delegates to |
| * {@link #processArtificialRescue(Annotation)} to complete the processing. |
| */ |
| private void processType(TypeDeclaration x) { |
| if (x.annotations == null) { |
| return; |
| } |
| |
| for (Annotation a : x.annotations) { |
| if (!ArtificialRescue.class.getName().equals( |
| CharOperation.toString(((ReferenceBinding) a.resolvedType).compoundName))) { |
| continue; |
| } |
| |
| // Sometimes it's a SingleMemberAnnotation, other times it's not |
| Expression value = null; |
| if (a instanceof SingleMemberAnnotation) { |
| value = ((SingleMemberAnnotation) a).memberValue; |
| } else { |
| for (MemberValuePair pair : a.memberValuePairs()) { |
| if ("value".equals(String.valueOf(pair.name))) { |
| value = pair.value; |
| break; |
| } |
| } |
| } |
| |
| assert value != null; |
| if (value instanceof ArrayInitializer) { |
| for (Expression e : ((ArrayInitializer) value).expressions) { |
| processArtificialRescue((Annotation) e); |
| } |
| } else if (value instanceof Annotation) { |
| processArtificialRescue((Annotation) value); |
| } else { |
| throw new InternalCompilerException( |
| "Unable to process annotation with value of type " |
| + value.getClass().getName()); |
| } |
| |
| return; |
| } |
| } |
| |
| private String[] stringArrayFromValue(Expression value) { |
| if (value instanceof StringLiteral) { |
| return new String[] {value.constant.stringValue()}; |
| } else if (value instanceof ArrayInitializer) { |
| ArrayInitializer init = (ArrayInitializer) value; |
| String[] toReturn = new String[init.expressions == null ? 0 |
| : init.expressions.length]; |
| for (int i = 0; i < toReturn.length; i++) { |
| toReturn[i] = init.expressions[i].constant.stringValue(); |
| } |
| return toReturn; |
| } else { |
| throw new InternalCompilerException("Unhandled value type " |
| + value.getClass().getName()); |
| } |
| } |
| } |
| |
| /** |
| * Check the {@link ArtificialRescue} annotations in a CompilationUnit. Errors |
| * are reported through {@link GWTProblem}. |
| */ |
| public static void check(CompilationUnitDeclaration cud, |
| boolean allowArtificialRescue) { |
| new ArtificialRescueChecker(cud, allowArtificialRescue).check(); |
| } |
| |
| /** |
| * Report all types named in {@link ArtificialRescue} annotations in a CUD. No |
| * error checking is done. |
| */ |
| public static List<String> collectReferencedTypes( |
| CompilationUnitDeclaration cud) { |
| return new ArtificialRescueChecker(cud).collect(); |
| } |
| |
| static String badMethodSignature(String method) { |
| return "Bad method signature " + method; |
| } |
| |
| static String nameAndTypesOnly() { |
| return "Only method name and parameter types expected"; |
| } |
| |
| static String noFieldsAllowed() { |
| return "Cannot refer to fields on array or primitive types"; |
| } |
| |
| static String noMethod(String className, String methodName) { |
| return "No method named " + methodName + " in type " + className; |
| } |
| |
| static String noMethodsAllowed() { |
| return "Cannot refer to methods on array or primitive types"; |
| } |
| |
| static String notFound(String className) { |
| return "Could not find type " + className; |
| } |
| |
| static String onlyGeneratedCode() { |
| return "The " + ArtificialRescue.class.getName() |
| + " annotation may only be used in generated code and its use" |
| + " by third parties is not supported."; |
| } |
| |
| static String unknownField(String field) { |
| return "Unknown field " + field; |
| } |
| |
| static String unknownProblem(String className, ProblemReferenceBinding problem) { |
| return "Unknown problem: " |
| + ProblemReferenceBinding.problemReasonString(problem.problemId()) |
| + " " + className; |
| } |
| |
| private final boolean allowArtificialRescue; |
| |
| private boolean collectTypes; |
| |
| private final CompilationUnitDeclaration cud; |
| |
| private List<String> referencedTypes; |
| |
| private boolean reportErrors; |
| |
| private ArtificialRescueChecker(CompilationUnitDeclaration cud) { |
| allowArtificialRescue = true; |
| this.cud = cud; |
| } |
| |
| private ArtificialRescueChecker(CompilationUnitDeclaration cud, |
| boolean allowArtificialRescue) { |
| this.cud = cud; |
| this.allowArtificialRescue = allowArtificialRescue; |
| } |
| |
| private void check() { |
| collectTypes = false; |
| reportErrors = true; |
| cud.traverse(new Visitor(), cud.scope); |
| } |
| |
| private List<String> collect() { |
| collectTypes = true; |
| referencedTypes = Lists.create(); |
| reportErrors = false; |
| cud.traverse(new Visitor(), cud.scope); |
| return referencedTypes; |
| } |
| |
| } |