All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.dev.javac.ArtificialRescueChecker Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * 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.
 * 
 * 
    *
  • (1) The ArtificialRescue annotation is only used in generated code.
  • *
  • (2) The className value names a type known to GWT (ignoring access rules) *
  • *
  • (3) The methods and fields of the type are known to GWT
  • *
*/ 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 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 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 collect() { collectTypes = true; referencedTypes = Lists.create(); reportErrors = false; cud.traverse(new Visitor(), cud.scope); return referencedTypes; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy