| /* |
| * 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.dev.javac; |
| |
| import com.google.gwt.dev.jdt.SafeASTVisitor; |
| import com.google.gwt.dev.util.InstalledHelpInfo; |
| |
| import org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.internal.compiler.ast.ASTNode; |
| import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; |
| import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; |
| import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; |
| 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.MethodScope; |
| import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Stack; |
| |
| /** |
| * Check a compilation unit for violations of |
| * {@link com.google.gwt.core.client.JavaScriptObject JavaScriptObject} (JSO) |
| * restrictions. The restrictions are summarized in |
| * <code>jsoRestrictions.html</code>. |
| * |
| * |
| * Any violations found are attached as errors on the |
| * CompilationUnitDeclaration. |
| * |
| * @see <a |
| * href="http://code.google.com/p/google-web-toolkit/wiki/OverlayTypes">Overlay |
| * types design doc</a> |
| * @see jsoRestrictions.html |
| */ |
| public class JSORestrictionsChecker { |
| |
| /** |
| * The order in which the checker will process types is undefined, so this |
| * type accumulates the information necessary for sanity-checking the JSO |
| * types. |
| */ |
| public static class CheckerState { |
| |
| private final Map<String, String> interfacesToJsoImpls = new HashMap<String, String>(); |
| |
| public void addJsoInterface(TypeDeclaration jsoType, |
| CompilationUnitDeclaration cud, ReferenceBinding interf) { |
| String intfName = CharOperation.toString(interf.compoundName); |
| String alreadyImplementor = interfacesToJsoImpls.get(intfName); |
| String myName = CharOperation.toString(jsoType.binding.compoundName); |
| if (alreadyImplementor == null) { |
| interfacesToJsoImpls.put(intfName, myName); |
| } else { |
| String msg = errAlreadyImplemented(intfName, alreadyImplementor, myName); |
| errorOn(jsoType, cud, msg); |
| } |
| } |
| |
| public String getJsoImplementor(ReferenceBinding binding) { |
| String name = CharOperation.toString(binding.compoundName); |
| return interfacesToJsoImpls.get(name); |
| } |
| |
| public boolean isJsoInterface(ReferenceBinding binding) { |
| String name = CharOperation.toString(binding.compoundName); |
| return interfacesToJsoImpls.containsKey(name); |
| } |
| } |
| |
| private class JSORestrictionsVisitor extends SafeASTVisitor implements |
| ClassFileConstants { |
| |
| private final Stack<Boolean> isJsoStack = new Stack<Boolean>(); |
| |
| @Override |
| public void endVisit(AllocationExpression exp, BlockScope scope) { |
| // In rare cases we might not be able to resolve the expression. |
| if (exp.type == null) { |
| return; |
| } |
| TypeBinding resolvedType = exp.resolvedType; |
| if (resolvedType == null) { |
| if (scope == null) { |
| return; |
| } |
| resolvedType = exp.type.resolveType(scope); |
| } |
| // Anywhere an allocation occurs is wrong. |
| if (isJsoSubclass(resolvedType)) { |
| errorOn(exp, ERR_NEW_JSO); |
| } |
| } |
| |
| @Override |
| public void endVisit(ConstructorDeclaration meth, ClassScope scope) { |
| if (!isJso()) { |
| return; |
| } |
| if ((meth.arguments != null) && (meth.arguments.length > 0)) { |
| errorOn(meth, ERR_CONSTRUCTOR_WITH_PARAMETERS); |
| } |
| if ((meth.modifiers & AccProtected) == 0) { |
| errorOn(meth, ERR_NONPROTECTED_CONSTRUCTOR); |
| } |
| if (meth.statements != null && meth.statements.length > 0) { |
| errorOn(meth, ERR_NONEMPTY_CONSTRUCTOR); |
| } |
| } |
| |
| @Override |
| public void endVisit(FieldDeclaration field, MethodScope scope) { |
| if (!isJso()) { |
| return; |
| } |
| if (!field.isStatic()) { |
| errorOn(field, ERR_INSTANCE_FIELD); |
| } |
| } |
| |
| @Override |
| public void endVisit(MethodDeclaration meth, ClassScope scope) { |
| if (!isJso()) { |
| return; |
| } |
| if ((meth.modifiers & (AccFinal | AccPrivate | AccStatic)) == 0) { |
| // The method's modifiers allow it to be overridden. Make |
| // one final check to see if the surrounding class is final. |
| if ((meth.scope == null) || !meth.scope.enclosingSourceType().isFinal()) { |
| errorOn(meth, ERR_INSTANCE_METHOD_NONFINAL); |
| } |
| } |
| |
| // Should not have to check isStatic() here, but isOverriding() appears |
| // to be set for static methods. |
| if (!meth.isStatic() |
| && (meth.binding != null && meth.binding.isOverriding())) { |
| errorOn(meth, ERR_OVERRIDDEN_METHOD); |
| } |
| } |
| |
| @Override |
| public void endVisit(TypeDeclaration type, ClassScope scope) { |
| popIsJso(); |
| } |
| |
| @Override |
| public void endVisit(TypeDeclaration type, CompilationUnitScope scope) { |
| popIsJso(); |
| } |
| |
| @Override |
| public void endVisitValid(TypeDeclaration type, BlockScope scope) { |
| popIsJso(); |
| } |
| |
| @Override |
| public boolean visit(TypeDeclaration type, ClassScope scope) { |
| pushIsJso(checkType(type)); |
| return true; |
| } |
| |
| @Override |
| public boolean visit(TypeDeclaration type, CompilationUnitScope scope) { |
| pushIsJso(checkType(type)); |
| return true; |
| } |
| |
| @Override |
| public boolean visitValid(TypeDeclaration type, BlockScope scope) { |
| pushIsJso(checkType(type)); |
| return true; |
| } |
| |
| private boolean checkType(TypeDeclaration type) { |
| SourceTypeBinding binding = type.binding; |
| if (!isJsoSubclass(binding)) { |
| return false; |
| } |
| |
| if (type.enclosingType != null && !binding.isStatic()) { |
| errorOn(type, ERR_IS_NONSTATIC_NESTED); |
| } |
| |
| ReferenceBinding[] interfaces = binding.superInterfaces(); |
| if (interfaces != null) { |
| for (ReferenceBinding interf : interfaces) { |
| if (interf.methods() == null) { |
| continue; |
| } |
| |
| if (interf.methods().length > 0) { |
| // See if any of my superTypes implement it. |
| ReferenceBinding superclass = binding.superclass(); |
| if (superclass == null |
| || !superclass.implementsInterface(interf, true)) { |
| state.addJsoInterface(type, cud, interf); |
| } |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| private boolean isJso() { |
| return isJsoStack.peek(); |
| } |
| |
| private void popIsJso() { |
| isJsoStack.pop(); |
| } |
| |
| private void pushIsJso(boolean isJso) { |
| isJsoStack.push(isJso); |
| } |
| } |
| |
| static final String ERR_CONSTRUCTOR_WITH_PARAMETERS = "Constructors must not have parameters in subclasses of JavaScriptObject"; |
| static final String ERR_INSTANCE_FIELD = "Instance fields cannot be used in subclasses of JavaScriptObject"; |
| static final String ERR_INSTANCE_METHOD_NONFINAL = "Instance methods must be 'final' in non-final subclasses of JavaScriptObject"; |
| static final String ERR_IS_NONSTATIC_NESTED = "Nested classes must be 'static' if they extend JavaScriptObject"; |
| static final String ERR_NEW_JSO = "'new' cannot be used to create instances of JavaScriptObject subclasses; instances must originate in JavaScript"; |
| static final String ERR_NONEMPTY_CONSTRUCTOR = "Constructors must be totally empty in subclasses of JavaScriptObject"; |
| static final String ERR_NONPROTECTED_CONSTRUCTOR = "Constructors must be 'protected' in subclasses of JavaScriptObject"; |
| static final String ERR_OVERRIDDEN_METHOD = "Methods cannot be overridden in JavaScriptObject subclasses"; |
| static final String JSO_CLASS = "com/google/gwt/core/client/JavaScriptObject"; |
| |
| /** |
| * Checks an entire |
| * {@link org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration}. |
| * |
| */ |
| public static void check(CheckerState state, CompilationUnitDeclaration cud) { |
| JSORestrictionsChecker checker = new JSORestrictionsChecker(state, cud); |
| checker.check(); |
| } |
| |
| /** |
| * Returns {@code true} if {@code typeBinding} is {@code JavaScriptObject} or |
| * any subtype. |
| */ |
| public static boolean isJso(TypeBinding typeBinding) { |
| if (!(typeBinding instanceof ReferenceBinding)) { |
| return false; |
| } |
| ReferenceBinding binding = (ReferenceBinding) typeBinding; |
| while (binding != null) { |
| if (JSO_CLASS.equals(String.valueOf(binding.constantPoolName()))) { |
| return true; |
| } |
| binding = binding.superclass(); |
| } |
| return false; |
| } |
| |
| /** |
| * Returns {@code true} if {@code typeBinding} is a subtype of |
| * {@code JavaScriptObject}, but not {@code JavaScriptObject} itself. |
| */ |
| public static boolean isJsoSubclass(TypeBinding typeBinding) { |
| if (!(typeBinding instanceof ReferenceBinding)) { |
| return false; |
| } |
| ReferenceBinding binding = (ReferenceBinding) typeBinding; |
| return isJso(binding.superclass()); |
| } |
| |
| static String errAlreadyImplemented(String intfName, String impl1, |
| String impl2) { |
| return "Only one JavaScriptObject type may implement the methods of an " |
| + "interface that declared methods. The interface (" + intfName |
| + ") is implemented by both (" + impl1 + ") and (" + impl2 + ")"; |
| } |
| |
| private static void errorOn(ASTNode node, CompilationUnitDeclaration cud, |
| String error) { |
| GWTProblem.recordError(node, cud, error, new InstalledHelpInfo( |
| "jsoRestrictions.html")); |
| } |
| |
| private final CompilationUnitDeclaration cud; |
| |
| private final CheckerState state; |
| |
| private JSORestrictionsChecker(CheckerState state, |
| CompilationUnitDeclaration cud) { |
| this.cud = cud; |
| this.state = state; |
| } |
| |
| private void check() { |
| cud.traverse(new JSORestrictionsVisitor(), cud.scope); |
| } |
| |
| private void errorOn(ASTNode node, String error) { |
| errorOn(node, cud, error); |
| } |
| } |