blob: 326d97319387608be4d5db3f235a9053dfd66893 [file] [log] [blame]
/*
* Copyright 2013 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 org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
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.QualifiedAllocationExpression;
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 java.util.ArrayList;
import java.util.List;
/**
* Handles the removal of GwtIncompatible annotated classes and members.
*/
public class GwtIncompatiblePreprocessor {
/**
* Checks whether GwtIncompatible is in the array of {@code Annotation}.
*
* @param annotations an (possible null) array of {@code Annotation}
* @return {@code true} if there is an annotation of class {@code *.GwtIncompatible} in
* array. {@code false} otherwise.
*/
private static boolean hasGwtIncompatibleAnnotation(Annotation[] annotations) {
if (annotations == null) {
return false;
}
for (Annotation ann : annotations) {
String typeName = new String(ann.type.getLastToken());
if (typeName.equals("GwtIncompatible")) {
return true;
}
}
return false;
}
/**
* Process all members of a type to remove any @GwtIncompatible one.
*/
private static void processMembers(TypeDeclaration tyDecl) {
processTypes(tyDecl.memberTypes);
processMethods(tyDecl);
processFields(tyDecl);
}
/**
* Remove @GwtIncompatible classes, members and recursively remove @GwtIncompatible
* inner classes. A dummy empty stub is retained for @GwtIncompatible classes.
*/
private static void processTypes(TypeDeclaration[] types) {
if (types == null) {
return;
}
for (TypeDeclaration tyDecl : types) {
if (!hasGwtIncompatibleAnnotation(tyDecl.annotations)) {
processMembers(tyDecl);
} else {
// Leave the empty class.
stripAllMembers(tyDecl);
}
}
}
/**
* Modifies the methods array of type {@code tyDecl} to remove any GwtIncompatible methods.
*/
private static void processMethods(TypeDeclaration tyDecl) {
if (tyDecl.methods == null) {
return;
}
List<AbstractMethodDeclaration> newMethods = new ArrayList<AbstractMethodDeclaration>();
for (AbstractMethodDeclaration methodDecl : tyDecl.methods) {
if (!hasGwtIncompatibleAnnotation(methodDecl.annotations)) {
newMethods.add(methodDecl);
}
}
if (newMethods.size() != tyDecl.methods.length) {
tyDecl.methods = newMethods.isEmpty() ? null : newMethods.toArray(
new AbstractMethodDeclaration[newMethods.size()]);
}
}
/**
* Modifies the fields array of type {@code tyDecl} to remove any GwtIncompatible fields.
*/
private static void processFields(TypeDeclaration tyDecl) {
if (tyDecl.fields == null) {
return;
}
List<FieldDeclaration> newFields = new ArrayList<FieldDeclaration>();
for (FieldDeclaration fieldDecl : tyDecl.fields) {
if (!hasGwtIncompatibleAnnotation(fieldDecl.annotations)) {
newFields.add(fieldDecl);
}
}
if (newFields.size() != tyDecl.fields.length) {
tyDecl.fields = newFields.isEmpty() ? null : newFields.toArray(new FieldDeclaration[newFields
.size()]);
}
}
/**
* Process inner classes, methods and fields from all anonymous inner classes.
*
* <p>Anonymous inner classes are represented inside expressions. Traverse the JDT AST removing
* anonymous inner classes in one go.
*/
private static void processAllAnonymousInnerClasses(CompilationUnitDeclaration cud) {
ASTVisitor visitor = new ASTVisitor() {
// Anonymous types are represented within the AST expression that creates the it.
@Override
public void endVisit(QualifiedAllocationExpression qualifiedAllocationExpression,
BlockScope scope) {
if (qualifiedAllocationExpression.anonymousType != null) {
processMembers(qualifiedAllocationExpression.anonymousType);
}
}
};
cud.traverse(visitor, cud.scope);
}
/**
* Removes all members of a class to leave it as an empty stub.
*/
private static void stripAllMembers(TypeDeclaration tyDecl) {
if (TypeDeclaration.kind(tyDecl.modifiers) == TypeDeclaration.ANNOTATION_TYPE_DECL) {
// Do not modify annotations at all.
return;
}
tyDecl.superclass = null;
tyDecl.superInterfaces = null;
tyDecl.annotations = null;
tyDecl.methods = null;
tyDecl.memberTypes = null;
tyDecl.fields = null;
if (TypeDeclaration.kind(tyDecl.modifiers) == TypeDeclaration.CLASS_DECL) {
// Create a default constructor so that the class is proper.
ConstructorDeclaration constructor = tyDecl.createDefaultConstructor(true, true);
// Mark only constructor as private so that it can not be instantiated.
constructor.modifiers = ClassFileConstants.AccPrivate;
// Clear a bit that is used for marking the constructor as default as it makes JDT
// assume that the constructor is public.
constructor.bits &= ~ASTNode.IsDefaultConstructor;
// Mark the class as final so that it can not be extended.
tyDecl.modifiers |= ClassFileConstants.AccFinal;
tyDecl.modifiers &= ~ClassFileConstants.AccAbstract;
}
}
/**
* Preprocess the compilation unit to remove @GwtIncompatible classes and members.
*/
public static void preproccess(CompilationUnitDeclaration cud) {
processTypes(cud.types);
processAllAnonymousInnerClasses(cud);
}
}