| /* |
| * 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.asm; |
| |
| import com.google.gwt.dev.javac.asmbridge.EmptyVisitor; |
| import com.google.gwt.dev.util.Name; |
| import com.google.gwt.dev.util.StringInterner; |
| |
| import org.objectweb.asm.AnnotationVisitor; |
| import org.objectweb.asm.FieldVisitor; |
| import org.objectweb.asm.MethodVisitor; |
| import org.objectweb.asm.Opcodes; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * A visitor (that collects class data from bytecode) and a model object to hold the collected data. |
| */ |
| public class CollectClassData extends EmptyVisitor { |
| |
| /** |
| * Holds the descriptor and value for an Enum-valued annotation. |
| */ |
| public static class AnnotationEnum { |
| private final String desc; |
| private final String value; |
| |
| /** |
| * Construct the value of an Enum-valued annotation. |
| * |
| * @param desc type descriptor of this enum |
| * @param value actual value in this enum |
| */ |
| public AnnotationEnum(String desc, String value) { |
| this.desc = StringInterner.get().intern(desc); |
| this.value = StringInterner.get().intern(value); |
| } |
| |
| /** |
| * @return the type descriptor of the enum type. |
| */ |
| public String getDesc() { |
| return desc; |
| } |
| |
| /** |
| * @return the annotation value. |
| */ |
| public String getValue() { |
| return value; |
| } |
| } |
| |
| /** |
| * Type of this class. |
| */ |
| public enum ClassType { |
| /** |
| * An anonymous inner class. |
| */ |
| Anonymous { |
| @Override |
| public boolean hasNoExternalName() { |
| return true; |
| } |
| }, |
| |
| /** |
| * A non-static named class nested inside another class. |
| */ |
| Inner { |
| @Override |
| public boolean hasHiddenConstructorArg() { |
| return true; |
| } |
| }, |
| |
| /** |
| * A named class defined inside a method. |
| */ |
| Local { |
| /* |
| * Note that we do not return true for hasHiddenConstructorArg since Local |
| * classes inside a static method will not have one and AFAICT there is no |
| * way to distinguish these cases without looking up the declaring method. |
| * However, since we are dropping any classes for which |
| * hasNoExternalName() returns true in TypeOracleUpdater.addNewUnits, it |
| * doesn't matter if we leave the synthetic argument in the list. |
| */ |
| |
| @Override |
| public boolean hasNoExternalName() { |
| return true; |
| } |
| }, |
| |
| /** |
| * A static nested class inside another class. |
| */ |
| Nested, |
| |
| /** |
| * A top level class named the same as its source file. |
| */ |
| TopLevel; |
| |
| /** |
| * @return true if this class type has a hidden constructor argument for the |
| * containing instance (ie, this$0). |
| */ |
| public boolean hasHiddenConstructorArg() { |
| return false; |
| } |
| |
| /** |
| * @return true if this class type is not visible outside a method. |
| */ |
| public boolean hasNoExternalName() { |
| return false; |
| } |
| } |
| |
| private int access; |
| |
| private final List<CollectAnnotationData> annotations = new ArrayList<CollectAnnotationData>(); |
| |
| private CollectClassData.ClassType classType = ClassType.TopLevel; |
| |
| private String enclosingInternalName; |
| |
| private String enclosingMethodDesc; |
| |
| private String enclosingMethodName; |
| private final List<CollectFieldData> fields = new ArrayList<CollectFieldData>(); |
| // internal names of interfaces |
| private String[] interfaceInternalNames; |
| // internal name |
| private String internalName; |
| // nested source name |
| private String nestedSourceName; |
| private final List<CollectMethodData> methods = new ArrayList<CollectMethodData>(); |
| private String signature; |
| private String source = null; |
| // internal name of superclass |
| private String superInternalName; |
| |
| /** |
| * Construct a visitor that will collect data about a class. |
| */ |
| public CollectClassData() { |
| } |
| |
| /** |
| * @return the access flags for this class (ie, bitwise or of Opcodes.ACC_*). |
| */ |
| public int getAccess() { |
| return access; |
| } |
| |
| public List<CollectAnnotationData> getAnnotations() { |
| return annotations; |
| } |
| |
| public CollectClassData.ClassType getClassType() { |
| return classType; |
| } |
| |
| public String getEnclosingInternalName() { |
| return enclosingInternalName; |
| } |
| |
| public String getEnclosingMethodDesc() { |
| return enclosingMethodDesc; |
| } |
| |
| public String getEnclosingMethodName() { |
| return enclosingMethodName; |
| } |
| |
| public List<CollectFieldData> getFields() { |
| return fields; |
| } |
| |
| /** |
| * @return an array of internal names of interfaces implemented by this class. |
| */ |
| public String[] getInterfaceInternalNames() { |
| return interfaceInternalNames; |
| } |
| |
| public String getInternalName() { |
| return internalName; |
| } |
| |
| public List<CollectMethodData> getMethods() { |
| return methods; |
| } |
| |
| public String getNestedSourceName() { |
| return nestedSourceName; |
| } |
| |
| public String getSignature() { |
| return signature; |
| } |
| |
| public String getSource() { |
| return source; |
| } |
| |
| public String getSuperInternalName() { |
| return superInternalName; |
| } |
| |
| /** |
| * @return true if this class has no external name (ie, is defined inside a |
| * method). |
| */ |
| public boolean hasNoExternalName() { |
| return classType.hasNoExternalName(); |
| } |
| |
| /** |
| * @return true if this class has no source name at all. |
| */ |
| public boolean isAnonymous() { |
| return classType == ClassType.Anonymous; |
| } |
| |
| @Override |
| public String toString() { |
| return "class " + internalName; |
| } |
| |
| /** |
| * Called at the beginning of visiting the class. |
| * |
| * @param version classfile version (ie, Opcodes.V1_5 etc) |
| * @param access access flags (ie, bitwise or of Opcodes.ACC_*) |
| * @param signature generic signature or null |
| * @param interfaces array of internal names of implemented interfaces |
| * @param internalName internal name of this class (ie, com/google/Foo) |
| * @param superInternalName internal name of superclass (ie, java/lang/Object) |
| */ |
| @Override |
| public void visit(int version, int access, String internalName, String signature, |
| String superInternalName, String[] interfaces) { |
| this.access = access; |
| assert Name.isInternalName(internalName); |
| this.internalName = internalName; |
| this.signature = signature; |
| this.superInternalName = superInternalName; |
| this.interfaceInternalNames = interfaces; |
| } |
| |
| @Override |
| public AnnotationVisitor visitAnnotation(String desc, boolean visible) { |
| CollectAnnotationData av = new CollectAnnotationData(desc, visible); |
| annotations.add(av); |
| return av; |
| } |
| |
| @Override |
| public void visitEnd() { |
| super.visitEnd(); |
| if (classType == ClassType.TopLevel) { |
| // top level source name calculation is trivial |
| nestedSourceName = internalName.substring(internalName.lastIndexOf('/') + 1); |
| } else if (classType == ClassType.Anonymous) { |
| nestedSourceName = null; |
| } |
| } |
| |
| /** |
| * Called for each field. |
| * |
| * @param access access flags for field |
| * @param name field name |
| * @param desc type descriptor (ie, Ljava/lang/String;) |
| * @param signature generic signature (null if not generic) |
| * @param value initialized value if constant |
| */ |
| @Override |
| public FieldVisitor visitField(int access, String name, String desc, |
| String signature, Object value) { |
| if ((access & Opcodes.ACC_SYNTHETIC) != 0) { |
| // if ("this$1".equals(name) && classType == ClassType.Anonymous) { |
| // // TODO(jat): !!! really nasty hack |
| // classType = ClassType.Inner; |
| // } |
| // skip synthetic fields |
| return null; |
| } |
| CollectFieldData fv = new CollectFieldData(access, name, desc, signature, |
| value); |
| fields.add(fv); |
| return fv; |
| } |
| |
| /** |
| * Called once for every inner class of this class. |
| * |
| * @param internalName internal name of inner class (ie, com/google/Foo$1) |
| * @param enclosingInternalName internal name of enclosing class (null if not a member |
| * class or anonymous) |
| * @param innerName simple name of the inner class (null if anonymous) |
| * @param access access flags (bitwise or of Opcodes.ACC_*) as declared in the |
| * enclosing class |
| */ |
| @Override |
| public void visitInnerClass(String internalName, String enclosingInternalName, String innerName, |
| int access) { |
| |
| buildNestedSourceName(internalName, enclosingInternalName, innerName); |
| |
| // If this inner class is ourselves, take the access flags defined in the InnerClass attribute. |
| if (this.internalName.equals(internalName)) { |
| if (enclosingInternalName != null) { |
| this.enclosingInternalName = enclosingInternalName; |
| } |
| this.access = access; |
| boolean isStatic = (access & Opcodes.ACC_STATIC) != 0; |
| switch (classType) { |
| case TopLevel: |
| classType = isStatic ? ClassType.Nested : ClassType.Inner; |
| break; |
| case Anonymous: |
| if (innerName != null) { |
| classType = ClassType.Local; |
| } |
| break; |
| case Inner: |
| // Already marked as inner class by the synthetic this$1 field |
| break; |
| default: |
| throw new IllegalStateException("Unexpected INNERCLASS with type of " |
| + classType); |
| } |
| } |
| } |
| |
| @Override |
| public MethodVisitor visitMethod(int access, String name, String desc, |
| String signature, String[] exceptions) { |
| if ((access & Opcodes.ACC_SYNTHETIC) != 0) { |
| // skip synthetic methods |
| return null; |
| } |
| CollectMethodData mv = new CollectMethodData(classType, access, name, desc, |
| signature, exceptions); |
| methods.add(mv); |
| return mv; |
| } |
| |
| @Override |
| public void visitOuterClass( |
| String enclosingInternalName, String enclosingMethodName, String enclosingMethodDesc) { |
| this.enclosingInternalName = enclosingInternalName; |
| this.enclosingMethodName = enclosingMethodName; |
| this.enclosingMethodDesc = enclosingMethodDesc; |
| classType = ClassType.Anonymous; // Could be Local, catch that later |
| } |
| |
| /** |
| * If compiled with debug, visit the source information. |
| * |
| * @param source unqualified filename containing source (ie, Foo.java) |
| * @param debug additional debug information (may be null) |
| */ |
| @Override |
| public void visitSource(String source, String debug) { |
| this.source = source; |
| } |
| |
| private void buildNestedSourceName(String internalName, String enclosingInternalName, |
| String innerName) { |
| if (classType == ClassType.Anonymous || enclosingInternalName == null) { |
| return; |
| } |
| |
| // ignores classes outside of this class' containment chain |
| if (!this.internalName.startsWith(internalName + "$") |
| && !this.internalName.equals(internalName)) { |
| return; |
| } |
| |
| if (nestedSourceName == null) { |
| // for 'com.Foo$Bar' in 'com.Foo', starts nestedSourceName as 'Foo' |
| nestedSourceName = |
| enclosingInternalName.substring(enclosingInternalName.lastIndexOf('/') + 1); |
| } |
| // tacks on the simple name, which might contain a '$' |
| nestedSourceName += "." + innerName; |
| } |
| } |