com.google.gwt.dev.javac.asm.CollectClassData Maven / Gradle / Ivy
The newest version!
/*
* 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 annotations = new ArrayList();
private CollectClassData.ClassType classType = ClassType.TopLevel;
private String enclosingInternalName;
private String enclosingMethodDesc;
private String enclosingMethodName;
private final List fields = new ArrayList();
// internal names of interfaces
private String[] interfaceInternalNames;
// internal name
private String internalName;
// nested source name
private String nestedSourceName;
private final List methods = new ArrayList();
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 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 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 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, getAccess());
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;
}
}