| /* |
| * 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.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.CompilationUnitDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; |
| import org.eclipse.jdt.internal.compiler.impl.BooleanConstant; |
| import org.eclipse.jdt.internal.compiler.impl.StringConstant; |
| import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.Binding; |
| 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.ElementValuePair; |
| import org.eclipse.jdt.internal.compiler.lookup.FieldBinding; |
| 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.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * 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.</li> |
| * <li>(3) The methods and fields of the type are known to GWT.</li> |
| * </ul> |
| */ |
| public abstract class ArtificialRescueChecker { |
| |
| /** |
| * Represents a single |
| * {@link com.google.gwt.core.client.impl.ArtificialRescue.Rescue}. |
| * <p> |
| * Only public so it can be used by |
| * {@link com.google.gwt.dev.jjs.impl.GenerateJavaAST}. |
| */ |
| public static class RescueData { |
| private static final RescueData[] EMPTY_RESCUEDATA = new RescueData[0]; |
| |
| public static RescueData[] createFromAnnotations(Annotation[] annotations) { |
| RescueData[] result = EMPTY_RESCUEDATA; |
| for (Annotation a : annotations) { |
| ReferenceBinding binding = (ReferenceBinding) a.resolvedType; |
| String name = CharOperation.toString(binding.compoundName); |
| if (!name.equals(ArtificialRescue.class.getName())) { |
| continue; |
| } |
| return createFromArtificialRescue(a); |
| } |
| return result; |
| } |
| |
| public static RescueData[] createFromArtificialRescue(Annotation artificialRescue) { |
| Object[] values = null; |
| RescueData[] result = EMPTY_RESCUEDATA; |
| for (ElementValuePair pair : artificialRescue.computeElementValuePairs()) { |
| if ("value".equals(String.valueOf(pair.getName()))) { |
| Object value = pair.getValue(); |
| if (value instanceof AnnotationBinding) { |
| values = new Object[]{value}; |
| } else { |
| values = (Object[]) value; |
| } |
| break; |
| } |
| } |
| assert values != null; |
| if (values.length > 0) { |
| result = new RescueData[values.length]; |
| for (int i = 0; i < result.length; ++i) { |
| result[i] = createFromRescue((AnnotationBinding) values[i]); |
| } |
| } |
| return result; |
| } |
| |
| private static RescueData createFromRescue(AnnotationBinding rescue) { |
| String className = null; |
| boolean instantiable = false; |
| String[] methods = Empty.STRINGS; |
| String[] fields = Empty.STRINGS; |
| for (ElementValuePair pair : rescue.getElementValuePairs()) { |
| String name = String.valueOf(pair.getName()); |
| if ("className".equals(name)) { |
| className = ((StringConstant) pair.getValue()).stringValue(); |
| } else if ("instantiable".equals(name)) { |
| BooleanConstant value = (BooleanConstant) pair.getValue(); |
| instantiable = value.booleanValue(); |
| } else if ("methods".equals(name)) { |
| methods = getValueAsStringArray(pair.getValue()); |
| } else if ("fields".equals(name)) { |
| fields = getValueAsStringArray(pair.getValue()); |
| } else { |
| assert false : "Unknown ArtificialRescue field"; |
| } |
| } |
| assert className != null; |
| return new RescueData(className, instantiable, fields, methods); |
| } |
| |
| private static String[] getValueAsStringArray(Object value) { |
| if (value instanceof StringConstant) { |
| return new String[]{((StringConstant) value).stringValue()}; |
| } |
| Object[] values = (Object[]) value; |
| String[] toReturn = new String[values.length]; |
| for (int i = 0; i < toReturn.length; ++i) { |
| toReturn[i] = ((StringConstant) values[i]).stringValue(); |
| } |
| return toReturn; |
| } |
| |
| private final String className; |
| private final String[] fields; |
| private final boolean instantiable; |
| private final String[] methods; |
| |
| RescueData(String className, boolean instantiable, String[] fields, String[] methods) { |
| this.className = className; |
| this.instantiable = instantiable; |
| this.methods = methods; |
| this.fields = fields; |
| } |
| |
| public String getClassName() { |
| return className; |
| } |
| |
| public String[] getFields() { |
| return fields; |
| } |
| |
| public String[] getMethods() { |
| return methods; |
| } |
| |
| public boolean isInstantiable() { |
| return instantiable; |
| } |
| } |
| |
| /** |
| * Checks that references are legal and resolve to a known element. Collects |
| * the rescued elements per type for later user. |
| */ |
| private static class Checker extends ArtificialRescueChecker { |
| private final Map<TypeDeclaration, Binding[]> artificialRescues; |
| private transient List<Binding> currentBindings = new ArrayList<Binding>(); |
| |
| public Checker(CompilationUnitDeclaration cud, Map<TypeDeclaration, Binding[]> artificialRescues) { |
| super(cud); |
| this.artificialRescues = artificialRescues; |
| } |
| |
| @Override |
| protected void processRescue(RescueData rescue) { |
| String className = rescue.getClassName(); |
| // Strip off any array-like extensions and just find base type |
| int arrayDims = 0; |
| while (className.endsWith("[]")) { |
| className = className.substring(0, className.length() - 2); |
| ++arrayDims; |
| } |
| |
| // Goal (2) The className value names a type known to GWT. |
| char[][] compoundName = CharOperation.splitOn('.', className.toCharArray()); |
| TypeBinding typeBinding = cud.scope.getType(compoundName, compoundName.length); |
| if (typeBinding == null) { |
| error(notFound(className)); |
| return; |
| } |
| if (typeBinding instanceof ProblemReferenceBinding) { |
| ProblemReferenceBinding problem = (ProblemReferenceBinding) typeBinding; |
| if (problem.problemId() == ProblemReasons.NotVisible) { |
| // Ignore |
| } else if (problem.problemId() == ProblemReasons.NotFound) { |
| error(notFound(className)); |
| } else { |
| error(unknownProblem(className, problem)); |
| } |
| return; |
| } |
| if (arrayDims > 0) { |
| typeBinding = cud.scope.createArrayType(typeBinding, arrayDims); |
| } |
| if (rescue.isInstantiable()) { |
| currentBindings.add(typeBinding); |
| } |
| if (typeBinding instanceof BaseTypeBinding || arrayDims > 0) { |
| // No methods or fields on primitive types or array types (3) |
| if (rescue.getMethods().length > 0) { |
| error(noMethodsAllowed()); |
| } |
| |
| if (rescue.getFields().length > 0) { |
| error(noFieldsAllowed()); |
| } |
| return; |
| } |
| |
| // Goal (3) The methods and fields of the type are known to GWT. |
| ReferenceBinding ref = (ReferenceBinding) typeBinding; |
| for (String field : rescue.getFields()) { |
| FieldBinding fieldBinding = ref.getField(field.toCharArray(), false); |
| if (fieldBinding == null) { |
| error(unknownField(field)); |
| } else { |
| currentBindings.add(fieldBinding); |
| } |
| } |
| for (String method : rescue.getMethods()) { |
| if (method.contains("@")) { |
| error(nameAndTypesOnly()); |
| continue; |
| } |
| // Method signatures use the same format as JSNI method refs. |
| JsniRef jsni = JsniRef.parse("@foo::" + method); |
| if (jsni == null) { |
| error(badMethodSignature(method)); |
| continue; |
| } |
| |
| MethodBinding[] methodBindings; |
| if (jsni.memberName().equals(String.valueOf(ref.compoundName[ref.compoundName.length - 1]))) { |
| // Constructor |
| methodBindings = ref.getMethods("<init>".toCharArray()); |
| } else { |
| methodBindings = ref.getMethods(jsni.memberName().toCharArray()); |
| } |
| boolean found = false; |
| for (MethodBinding methodBinding : methodBindings) { |
| if (jsni.matchesAnyOverload() || jsni.paramTypesString().equals(sig(methodBinding))) { |
| currentBindings.add(methodBinding); |
| found = true; |
| } |
| } |
| if (!found) { |
| error(noMethod(className, jsni.memberSignature())); |
| continue; |
| } |
| } |
| } |
| |
| @Override |
| protected void processType(TypeDeclaration x) { |
| super.processType(x); |
| if (currentBindings.size() > 0) { |
| Binding[] result = currentBindings.toArray(new Binding[currentBindings.size()]); |
| artificialRescues.put(x, result); |
| currentBindings = new ArrayList<Binding>(); |
| } |
| } |
| |
| private String sig(MethodBinding methodBinding) { |
| StringBuilder sb = new StringBuilder(); |
| for (TypeBinding paramType : methodBinding.parameters) { |
| sb.append(paramType.signature()); |
| } |
| return sb.toString(); |
| } |
| } |
| |
| /** |
| * Collects only the names of the rescued types; does not report errors. |
| */ |
| private static class Collector extends ArtificialRescueChecker { |
| private final List<String> referencedTypes; |
| |
| public Collector(CompilationUnitDeclaration cud, List<String> referencedTypes) { |
| super(cud); |
| this.referencedTypes = referencedTypes; |
| } |
| |
| @Override |
| protected void processRescue(RescueData rescue) { |
| String className = rescue.getClassName(); |
| while (className.endsWith("[]")) { |
| className = className.substring(0, className.length() - 2); |
| } |
| referencedTypes.add(className); |
| } |
| } |
| |
| /** |
| * Records an error if artificial rescues are used at all. |
| */ |
| private static class Disallowed extends ArtificialRescueChecker { |
| public Disallowed(CompilationUnitDeclaration cud) { |
| super(cud); |
| } |
| |
| @Override |
| protected void processRescue(RescueData rescue) { |
| // Goal (1) ArtificialRescue annotation is only used in generated code. |
| error(onlyGeneratedCode()); |
| } |
| } |
| |
| private class Visitor extends SafeASTVisitor { |
| @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); |
| } |
| } |
| |
| /** |
| * Check the {@link ArtificialRescue} annotations in a CompilationUnit. Errors |
| * are reported through {@link GWTProblem}. |
| */ |
| public static void check(CompilationUnitDeclaration cud, boolean allowArtificialRescue, |
| Map<TypeDeclaration, Binding[]> artificialRescues) { |
| if (allowArtificialRescue) { |
| new Checker(cud, artificialRescues).exec(); |
| } else { |
| new Disallowed(cud).exec(); |
| } |
| } |
| |
| /** |
| * Report all types named in {@link ArtificialRescue} annotations in a CUD. No |
| * error checking is done. |
| */ |
| public static List<String> collectReferencedTypes(CompilationUnitDeclaration cud) { |
| ArrayList<String> result = new ArrayList<String>(); |
| new Collector(cud, result).exec(); |
| return Lists.normalize(result); |
| } |
| |
| 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 methodSig) { |
| return "No method " + methodSig + " 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; |
| } |
| |
| protected final CompilationUnitDeclaration cud; |
| |
| private Annotation errorNode; |
| |
| private ArtificialRescueChecker(CompilationUnitDeclaration cud) { |
| this.cud = cud; |
| } |
| |
| protected final void error(String msg) { |
| GWTProblem.recordError(errorNode, cud, msg, null); |
| } |
| |
| protected final void exec() { |
| cud.traverse(new Visitor(), cud.scope); |
| } |
| |
| protected abstract void processRescue(RescueData rescue); |
| |
| /** |
| * Examine a TypeDeclaration for ArtificialRescue annotations. |
| */ |
| protected void processType(TypeDeclaration x) { |
| if (x.annotations == null) { |
| return; |
| } |
| for (Annotation a : x.annotations) { |
| ReferenceBinding binding = (ReferenceBinding) a.resolvedType; |
| String name = CharOperation.toString(binding.compoundName); |
| if (!name.equals(ArtificialRescue.class.getName())) { |
| continue; |
| } |
| errorNode = a; |
| RescueData[] rescues = RescueData.createFromArtificialRescue(a); |
| for (RescueData rescue : rescues) { |
| processRescue(rescue); |
| } |
| break; |
| } |
| } |
| } |