/* * 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.devtools.treeshaker; import com.google.devtools.j2objc.ast.AbstractTypeDeclaration; import com.google.devtools.j2objc.ast.ClassInstanceCreation; import com.google.devtools.j2objc.ast.CompilationUnit; import com.google.devtools.j2objc.ast.ConstructorInvocation; import com.google.devtools.j2objc.ast.EnumDeclaration; import com.google.devtools.j2objc.ast.MethodDeclaration; import com.google.devtools.j2objc.ast.MethodInvocation; import com.google.devtools.j2objc.ast.SuperConstructorInvocation; import com.google.devtools.j2objc.ast.TreeUtil; import com.google.devtools.j2objc.ast.TypeDeclaration; import com.google.devtools.j2objc.ast.UnitTreeVisitor; import com.google.devtools.j2objc.ast.VariableDeclarationFragment; import com.google.devtools.j2objc.util.CodeReferenceMap.Builder; import com.google.devtools.j2objc.util.ElementUtil; import com.google.devtools.j2objc.util.TypeUtil; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import org.eclipse.jdt.core.dom.Modifier; /** * Reference-mapping code for TreeShaker functionality that uses the visitor pattern * to identify all elements in the source code * * @author Priyank Malvania */ public class ElementReferenceMapper extends UnitTreeVisitor { abstract static class ReferenceNode { protected boolean reachable = false; public abstract String getUniqueID(); public abstract void addToBuilder(Builder builder); public abstract boolean isDead(); } class ClassReferenceNode extends ReferenceNode { final TypeElement classElement; boolean containsPublicField = false; public ClassReferenceNode(TypeElement classElement) { this.classElement = classElement; } @Override public String getUniqueID() { return stitchClassIdentifier(classElement); } @Override public void addToBuilder(Builder builder) { builder.addClass(elementUtil.getBinaryName(classElement)); } /** * Returns whether the class is unused or not. * Classes that contain public fields can possibly be referenced by the field indirectly, * so we want to keep those class source files. */ //TODO(malvania): When FieldAccess detection is supported, mark that class as reachable there, // and remove the containsPublicField flag here. @Override public boolean isDead() { return !(reachable || containsPublicField); } } class FieldReferenceNode extends ReferenceNode { final VariableDeclarationFragment fieldFragment; final boolean isPublic; public FieldReferenceNode(VariableDeclarationFragment fieldFragment) { this.fieldFragment = fieldFragment; this.isPublic = !ElementUtil.isPrivate(fieldFragment.getVariableElement()); } @Override public String getUniqueID() { return stitchFieldIdentifier(fieldFragment); } @Override public void addToBuilder(Builder builder) { //TODO(malvania): Enable the following code when the FieldAccess use-marking is done. //String className = elementUtil.getBinaryName( // ElementUtil.getDeclaringClass(fieldFragment.getVariableElement())); //String fragmentIdentifier = fieldFragment.getName().getIdentifier(); //builder.addDeadField(className, fragmentIdentifier); } @Override public boolean isDead() { return !reachable; } } class MethodReferenceNode extends ReferenceNode { final ExecutableElement methodElement; boolean invoked = false; boolean declared = false; Set<String> invokedMethods; Set<String> overridingMethods; public MethodReferenceNode(ExecutableElement methodElement) { this.methodElement = methodElement; this.invokedMethods = new HashSet<String>(); this.overridingMethods = new HashSet<String>(); } @Override public String getUniqueID() { return stitchMethodIdentifier(methodElement); } @Override public void addToBuilder(Builder builder) { String className = elementUtil.getBinaryName(ElementUtil.getDeclaringClass(methodElement)); String methodName = typeUtil.getReferenceName(methodElement); String methodSignature = typeUtil.getReferenceSignature(methodElement); builder.addMethod(className, methodName, methodSignature); } /** * Returns whether the method is unused or not. * Methods that are invoked but not declared are .class methods not included in source files. * Hence, they should not be added to the CodeReferenceMap at all. */ @Override public boolean isDead() { return !(reachable || (invoked && !declared)); } } private final HashMap<String, ReferenceNode> elementReferenceMap; private final Set<String> staticSet; private final HashMap<String, Set<String>> overrideMap; public ElementReferenceMapper(CompilationUnit unit, HashMap<String, ReferenceNode> elementReferenceMap, Set<String> staticSet, HashMap<String, Set<String>> overrideMap) { super(unit); this.elementReferenceMap = elementReferenceMap; this.staticSet = staticSet; this.overrideMap = overrideMap; } @Override public void endVisit(EnumDeclaration node) { visitType(node); } @Override public void endVisit(TypeDeclaration node) { visitType(node); } private void visitType(AbstractTypeDeclaration node) { TypeElement type = node.getTypeElement(); elementReferenceMap.putIfAbsent(stitchClassIdentifier(type), new ClassReferenceNode(type)); } //TODO(malvania): Add the field type class to reference classes. //Currently, jdt only supports well known types. Soon, we can get type mirror from field //and resolve the type by its name using a resolve method in the parser environment. @Override public void endVisit(VariableDeclarationFragment fragment) { //TODO(malvania): Add field to elementReferenceMap when field detection is enabled and the // ElementUtil.getBinaryName() method doesn't break when called on a static block's // ExecutableElement. //String fieldID = stitchFieldIdentifier(fragment); //elementReferenceMap.putIfAbsent(fieldID, new FieldReferenceNode(fragment)); Element element = fragment.getVariableElement().getEnclosingElement(); if (element instanceof TypeElement) { TypeElement type = (TypeElement) element; if (ElementUtil.isPublic(fragment.getVariableElement())) { ClassReferenceNode node = (ClassReferenceNode) elementReferenceMap .get(stitchClassIdentifier(type)); if (node == null) { node = new ClassReferenceNode(type); } node.containsPublicField = true; elementReferenceMap.putIfAbsent(stitchClassIdentifier(type), node); } } } /** * Adds a node for the child in the elementReferenceMap if it doesn't exist, marks it as declared, * and adds this method to the override map. * @param methodElement */ private void handleChildMethod(ExecutableElement methodElement) { String methodIdentifier = stitchMethodIdentifier(methodElement); MethodReferenceNode node = (MethodReferenceNode) elementReferenceMap.get(methodIdentifier); if (node == null) { node = new MethodReferenceNode(methodElement); } node.invoked = true; elementReferenceMap.put(methodIdentifier, node); addToOverrideMap(methodElement); } /** * Adds a node for the parent in the elementReferenceMap if it doesn't exist, adds the method to * the override map, and links the child method in the invokedMethods set. * @param parentMethodElement * @param childMethodElement */ private void handleParentMethod(ExecutableElement parentMethodElement, ExecutableElement childMethodElement) { MethodReferenceNode parentMethodNode = (MethodReferenceNode) elementReferenceMap .get(stitchMethodIdentifier(parentMethodElement)); if (parentMethodNode == null) { parentMethodNode = new MethodReferenceNode(parentMethodElement); } parentMethodNode.invokedMethods.add(stitchMethodIdentifier(childMethodElement)); elementReferenceMap.put(stitchMethodIdentifier(parentMethodElement), parentMethodNode); addToOverrideMap(parentMethodElement); } /** * When a constructor in invoked (including a default constructor), adds the constructor and * invoking method to elementReferenceMap. The class will eventually be marked as used. * Counts as both the declaration (in class) and invocation (new _()) of the constructor. */ @Override public void endVisit(ClassInstanceCreation instance) { ExecutableElement childMethodElement = instance.getExecutableElement(); handleChildMethod(childMethodElement); MethodDeclaration parentMethodDeclaration = TreeUtil.getEnclosingMethod(instance); if (parentMethodDeclaration == null) { staticSet.add(stitchMethodIdentifier(childMethodElement)); return; } ExecutableElement parentMethodElement = parentMethodDeclaration.getExecutableElement(); handleParentMethod(parentMethodElement, childMethodElement); } @Override public void endVisit(ConstructorInvocation invocation) { ExecutableElement childMethodElement = invocation.getExecutableElement(); handleChildMethod(childMethodElement); MethodDeclaration parentMethodDeclaration = TreeUtil.getEnclosingMethod(invocation); if (parentMethodDeclaration == null) { staticSet.add(stitchMethodIdentifier(childMethodElement)); return; } ExecutableElement parentMethodElement = parentMethodDeclaration.getExecutableElement(); handleParentMethod(parentMethodElement, childMethodElement); } @Override public void endVisit(SuperConstructorInvocation invocation) { ExecutableElement childMethodElement = invocation.getExecutableElement(); handleChildMethod(childMethodElement); MethodDeclaration parentMethodDeclaration = TreeUtil.getEnclosingMethod(invocation); if (parentMethodDeclaration == null) { staticSet.add(stitchMethodIdentifier(childMethodElement)); return; } ExecutableElement parentMethodElement = parentMethodDeclaration.getExecutableElement(); handleParentMethod(parentMethodElement, childMethodElement); } @Override public void endVisit(MethodDeclaration method) { if (Modifier.isNative(method.getModifiers())) { return; } ExecutableElement methodElement = method.getExecutableElement(); String methodIdentifier = stitchMethodIdentifier(methodElement); MethodReferenceNode node = (MethodReferenceNode) elementReferenceMap.get(methodIdentifier); if (node == null) { node = new MethodReferenceNode(methodElement); } node.declared = true; elementReferenceMap.put(stitchMethodIdentifier(methodElement), node); addToOverrideMap(methodElement); } @Override public void endVisit(MethodInvocation method) { ExecutableElement childMethodElement = method.getExecutableElement(); handleChildMethod(childMethodElement); MethodDeclaration parentMethodDeclaration = TreeUtil.getEnclosingMethod(method); if (parentMethodDeclaration == null) { staticSet.add(stitchMethodIdentifier(childMethodElement)); return; } ExecutableElement parentMethodElement = parentMethodDeclaration.getExecutableElement(); handleParentMethod(parentMethodElement, childMethodElement); } /** * Adds the common IDs of overriding methods (methodName and signature) to the override map. * @param methodElement */ private void addToOverrideMap(ExecutableElement methodElement) { String overrideID = stitchOverrideMethodIdentifier(methodElement); if (overrideMap.containsKey(overrideID)) { overrideMap.get(overrideID).add(stitchMethodIdentifier(methodElement)); } else { HashSet<String> overrideSet = new HashSet<String>(); overrideSet.add(stitchMethodIdentifier(methodElement)); overrideMap.put(overrideID, overrideSet); } } public String stitchClassIdentifier(TypeElement elem) { return stitchClassIdentifier(elem, elementUtil); } public static String stitchClassIdentifier(TypeElement elem, ElementUtil inputElementUtil) { return stitchClassIdentifier(inputElementUtil.getBinaryName(elem)); } public static String stitchClassIdentifier(String className) { StringBuilder sb = new StringBuilder("["); sb.append(className); sb.append("]"); return sb.toString(); } public String stitchFieldIdentifier(VariableDeclarationFragment fragment) { return stitchFieldIdentifier(fragment, elementUtil); } public static String stitchFieldIdentifier(VariableDeclarationFragment fragment, ElementUtil inputElementUtil) { TypeElement declaringClass = ElementUtil.getDeclaringClass(fragment.getVariableElement()); String className = inputElementUtil.getBinaryName(declaringClass); String fragmentIdentifier = ElementUtil.getName(fragment.getVariableElement()); return stitchFieldIdentifier(className, fragmentIdentifier); } public static String stitchFieldIdentifier(String className, String fieldName) { StringBuilder sb = new StringBuilder("["); sb.append(className); sb.append(","); sb.append(fieldName); sb.append("]"); return sb.toString(); } public String stitchOverrideMethodIdentifier(ExecutableElement methodElement) { return stitchOverrideMethodIdentifier(methodElement, typeUtil); } public static String stitchOverrideMethodIdentifier(ExecutableElement methodElement, TypeUtil inputTypeUtil) { String methodName = inputTypeUtil.getReferenceName(methodElement); String methodSignature = inputTypeUtil.getReferenceSignature(methodElement); return stitchOverrideMethodIdentifier(methodName, methodSignature); } public static String stitchOverrideMethodIdentifier(String methodName, String signature) { StringBuilder sb = new StringBuilder("["); sb.append(methodName); sb.append(","); sb.append(signature); sb.append("]"); return sb.toString(); } public String stitchMethodIdentifier(ExecutableElement methodElement) { return stitchMethodIdentifier(methodElement, typeUtil, elementUtil); } public static String stitchMethodIdentifier(ExecutableElement methodElement, TypeUtil inputTypeUtil, ElementUtil inputElementUtil) { String className = inputElementUtil.getBinaryName(ElementUtil.getDeclaringClass(methodElement)); String methodName = inputTypeUtil.getReferenceName(methodElement); String methodSignature = inputTypeUtil.getReferenceSignature(methodElement); return stitchMethodIdentifier(className, methodName, methodSignature); } public static String stitchMethodIdentifier(String className, String methodName, String signature) { StringBuilder sb = new StringBuilder("["); sb.append(className); sb.append(","); sb.append(methodName); sb.append(","); sb.append(signature); sb.append("]"); return sb.toString(); } }