blob: b0879d3db17897b4f842d8fa11bbc6492186acf8 [file] [log] [blame]
/*
* 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 final 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 final List<CollectMethodData> methods = new ArrayList<CollectMethodData>();
private final 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;
}
}