| /* |
| * 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.asm.AnnotationVisitor; |
| import com.google.gwt.dev.asm.FieldVisitor; |
| import com.google.gwt.dev.asm.MethodVisitor; |
| import com.google.gwt.dev.asm.Opcodes; |
| import com.google.gwt.dev.asm.commons.EmptyVisitor; |
| import com.google.gwt.dev.util.Name; |
| import com.google.gwt.dev.util.StringInterner; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Reads the bytecode for a class and collects data needed for building |
| * TypeOracle structures. |
| */ |
| public class CollectClassData extends EmptyVisitor { |
| |
| /** |
| * Type of this class. |
| */ |
| public enum ClassType { |
| /** |
| * A top level class named the same as its source file. |
| */ |
| TopLevel, |
| |
| /** |
| * A non-static named class nested inside another class. |
| */ |
| Inner { |
| @Override |
| public boolean hasHiddenConstructorArg() { |
| return true; |
| } |
| }, |
| |
| /** |
| * A static nested class inside another class. |
| */ |
| Nested, |
| |
| /** |
| * An anonymous inner class. |
| */ |
| Anonymous { |
| @Override |
| public boolean hasNoExternalName() { |
| 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 TypeOracleMediator.addNewUnits, it |
| * doesn't matter if we leave the synthetic argument in the list. |
| */ |
| |
| @Override |
| public boolean hasNoExternalName() { |
| return true; |
| } |
| }; |
| |
| /** |
| * @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; |
| } |
| } |
| |
| /** |
| * 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; |
| } |
| } |
| |
| private List<CollectAnnotationData> annotations = new ArrayList<CollectAnnotationData>(); |
| |
| private String source = null; |
| |
| // internal name |
| private String name; |
| |
| private String signature; |
| |
| // internal name of superclass |
| private String superName; |
| |
| // internal names of interfaces |
| private String[] interfaces; |
| private List<CollectMethodData> methods = new ArrayList<CollectMethodData>(); |
| private List<CollectFieldData> fields = new ArrayList<CollectFieldData>(); |
| private int access; |
| private String outerClass; |
| private String outerMethodName; |
| private String outerMethodDesc; |
| private CollectClassData.ClassType classType = ClassType.TopLevel; |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * @return a list of annotations on this class. |
| */ |
| public List<CollectAnnotationData> getAnnotations() { |
| return annotations; |
| } |
| |
| /** |
| * @return the class type. |
| */ |
| public CollectClassData.ClassType getClassType() { |
| return classType; |
| } |
| |
| /** |
| * @return a list of fields in this class. |
| */ |
| public List<CollectFieldData> getFields() { |
| return fields; |
| } |
| |
| /** |
| * @return an array of internal names of interfaces implemented by this class. |
| */ |
| public String[] getInterfaces() { |
| return interfaces; |
| } |
| |
| /** |
| * @return the methods |
| */ |
| public List<CollectMethodData> getMethods() { |
| return methods; |
| } |
| |
| /** |
| * @return the name |
| */ |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * @return the outerClass |
| */ |
| public String getOuterClass() { |
| return outerClass; |
| } |
| |
| /** |
| * @return the outerMethodDesc |
| */ |
| public String getOuterMethodDesc() { |
| return outerMethodDesc; |
| } |
| |
| /** |
| * @return the outerMethodName |
| */ |
| public String getOuterMethodName() { |
| return outerMethodName; |
| } |
| |
| /** |
| * @return the signature |
| */ |
| public String getSignature() { |
| return signature; |
| } |
| |
| /** |
| * @return the source |
| */ |
| public String getSource() { |
| return source; |
| } |
| |
| /** |
| * @return the superName |
| */ |
| public String getSuperName() { |
| return superName; |
| } |
| |
| /** |
| * @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 " + name; |
| } |
| |
| /** |
| * 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 name internal name of this class (ie, com/google/Foo) |
| * @param signature generic signature or null |
| * @param superName binary name of superclass (ie, java/lang/Object) |
| * @param interfaces array of binary names of implemented interfaces |
| */ |
| @Override |
| public void visit(int version, int access, String name, String signature, |
| String superName, String[] interfaces) { |
| this.access = access; |
| assert Name.isInternalName(name); |
| this.name = name; |
| this.signature = signature; |
| this.superName = superName; |
| this.interfaces = interfaces; |
| } |
| |
| @Override |
| public AnnotationVisitor visitAnnotation(String desc, boolean visible) { |
| CollectAnnotationData av = new CollectAnnotationData(desc, |
| visible); |
| annotations.add(av); |
| return av; |
| } |
| |
| /** |
| * 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 name internal name of inner class (ie, com/google/Foo$1) |
| * @param outerName 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 name, String outerName, String innerName, |
| int access) { |
| // If this inner class is ourselves, merge the access flags, since |
| // static, for example, only appears in the InnerClass attribute. |
| if (this.name.equals(name)) { |
| if (outerName != null) { |
| outerClass = outerName; |
| } |
| // TODO(jat): should we only pull in a subset of these flags? Use only |
| // these flags, or what? For now, just grab ACC_STATIC and ACC_PRIVATE |
| int copyFlags = access & (Opcodes.ACC_STATIC | Opcodes.ACC_PRIVATE); |
| this.access |= copyFlags; |
| 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 owner, String name, String desc) { |
| this.outerClass = owner; |
| this.outerMethodName = name; |
| this.outerMethodDesc = desc; |
| 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; |
| } |
| } |