blob: a83f0f78775b2eb2399a388c47df3bf4f60c50a0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.felix.ipojo.manipulation;
import org.objectweb.asm.*;
import java.util.*;
/**
* Checks that a POJO is already manipulated or not.
* Moreover it allows to get manipulation data about this class.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class ClassChecker extends ClassVisitor implements Opcodes {
/**
* True if the class is already manipulated.
*/
private boolean m_isAlreadyManipulated = false;
/**
* Interfaces implemented by the component.
*/
private List<String> m_itfs = new ArrayList<String>();
/**
* Field map [field name, type] discovered in the component class.
*/
private Map<String, String> m_fields = new TreeMap<String, String>();
/**
* Method List of method descriptor discovered in the component class.
*/
private List<MethodDescriptor> m_methods = new ArrayList<MethodDescriptor>();
/**
* Super class if not java.lang.Object.
*/
private String m_superClass;
/**
* Class name.
*/
private String m_className;
/**
* List of visited inner class owned by the implementation class.
*/
private Map<String, List<MethodDescriptor>> m_inners = new LinkedHashMap<String, List<MethodDescriptor>>();
/**
* Class Version.
* Used to determine the frame format.
*/
private int m_classVersion;
public ClassChecker() {
super(Opcodes.ASM5);
}
/**
* Check if the _cm field already exists.
* Update the field list.
*
* @param access : access of the field
* @param name : name of the field
* @param desc : description of the field
* @param signature : signature of the field
* @param value : value of the field (for static field only)
* @return the field visitor
* @see org.objectweb.asm.ClassVisitor#visitField(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object)
*/
public FieldVisitor visitField(int access, String name, String desc,
String signature, Object value) {
if (name.equals(ClassManipulator.IM_FIELD)
&& desc.equals("Lorg/apache/felix/ipojo/InstanceManager;")) {
m_isAlreadyManipulated = true;
} else if (name.startsWith("class$")) { // Does not add class$* field generated by 'x.class'
return null;
} else if ((access & ACC_STATIC) == ACC_STATIC) {
return null;
}
if (isManipulatedField(name)) {
return null;
}
Type type = Type.getType(desc);
if (type.getSort() == Type.ARRAY) {
if (type.getInternalName().startsWith("L")) {
String internalType = type.getInternalName().substring(1);
String nameType = internalType.replace('/', '.');
m_fields.put(name, nameType + "[]");
} else {
String nameType = type.getClassName().substring(0,
type.getClassName().length() - 2);
m_fields.put(name, nameType + "[]");
}
} else {
m_fields.put(name, type.getClassName());
}
return null;
}
private boolean isManipulatedField(String name) {
return ((ClassManipulator.IM_FIELD.equals(name))
|| (name.startsWith(ClassManipulator.FIELD_FLAG_PREFIX))
|| (name.startsWith(ClassManipulator.METHOD_FLAG_PREFIX)));
}
/**
* Add the inner class to the list of inner class to manipulate.
* The method checks that the inner class is really owned by the implementation class.
*
* @param name inner class qualified name
* @param outerName outer class name (may be null for anonymous class)
* @param innerName inner class simple (i.e. short) name
* @param access inner class visibility
* @see org.objectweb.asm.ClassVisitor#visitInnerClass(java.lang.String, java.lang.String, java.lang.String, int)
*/
public void visitInnerClass(String name, String outerName, String innerName, int access) {
if (m_className.equals(outerName) || outerName == null) { // Anonymous classes does not have an outer class.
// Do not include inner static class
if (!((access & ACC_STATIC) == ACC_STATIC)) {
m_inners.put(name, new ArrayList<MethodDescriptor>());
}
}
}
/**
* Check if the class was already manipulated.
*
* @return true if the class is already manipulated.
*/
public boolean isAlreadyManipulated() {
return m_isAlreadyManipulated;
}
/**
* Gets the extracted class version
*
* @return the class version.
*/
public int getClassVersion() {
return m_classVersion;
}
/**
* Visit the class.
* Update the implemented interface list.
*
* @param version : version of the class
* @param access : access of the class
* @param name : name of the class
* @param signature : signature of the class
* @param superName : super class of the class
* @param interfaces : implemented interfaces.
* @see org.objectweb.asm.ClassVisitor#visit(int, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String[])
*/
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
m_classVersion = version;
if (!superName.equals("java/lang/Object")) {
m_superClass = superName.replace('/', '.');
}
for (String anInterface : interfaces) {
if (!anInterface.equals("org/apache/felix/ipojo/Pojo")) {
m_itfs.add(anInterface.replace('/', '.'));
}
}
m_className = name;
}
/**
* Visit a method.
* Update the method list (except if it init or clinit.
*
* @param access - the method's access flags (see Opcodes). This parameter also indicates if the method is synthetic and/or deprecated.
* @param name - the method's name.
* @param desc - the method's descriptor (see Type).
* @param signature - the method's signature. May be null if the method parameters, return type and exceptions do not use generic types.
* @param exceptions - the internal names of the method's exception classes (see getInternalName). May be null.
* @return nothing.
* @see org.objectweb.asm.ClassVisitor#visitMethod(int, java.lang.String, java.lang.String, java.lang.String, java.lang.String[])
*/
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
if (!name.equals("<clinit>")) {
if (name.equals("<init>")) {
if (!isGeneratedConstructor(name, desc)) {
final MethodDescriptor md = new MethodDescriptor("$init", desc, (access & ACC_STATIC) == ACC_STATIC);
m_methods.add(md);
return new MethodInfoCollector(md);
}
} else {
// no constructors.
if (!isGeneratedMethod(name, desc)) {
final MethodDescriptor md = new MethodDescriptor(name, desc, (access & ACC_STATIC) == ACC_STATIC);
m_methods.add(md);
return new MethodInfoCollector(md);
}
}
}
if (name.equals("<clinit>")) {
return new InnerClassAssignedToStaticFieldDetector();
}
return null;
}
public static boolean isGeneratedConstructor(String name, String desc) {
return ("<init>".equals(name) && isFirstArgumentInstanceManager(desc));
}
public static boolean isFirstArgumentInstanceManager(String desc) {
Type[] types = Type.getArgumentTypes(desc);
return types != null && (types.length >= 1)
&& Type.getType("Lorg/apache/felix/ipojo/InstanceManager;").equals(types[0]);
}
public static boolean isGeneratedMethod(String name, String desc) {
return isGetterMethod(name, desc)
|| isSetterMethod(name, desc)
|| isSetInstanceManagerMethod(name)
|| isGetComponentInstanceMethod(name, desc)
|| isManipulatedMethod(name);
}
private static boolean isGetterMethod(String name, String desc) {
// TYPE __getXXX()
Type[] arguments = Type.getArgumentTypes(desc);
return (name.startsWith("__get")
&& (arguments.length == 0)
&& !Type.VOID_TYPE.equals(Type.getReturnType(desc)));
}
private static boolean isSetterMethod(String name, String desc) {
// void __setXXX(TYPE)
Type[] arguments = Type.getArgumentTypes(desc);
return (name.startsWith("__set")
&& (arguments.length == 1)
&& Type.VOID_TYPE.equals(Type.getReturnType(desc)));
}
private static boolean isSetInstanceManagerMethod(String name) {
return name.startsWith("_setInstanceManager");
}
private static boolean isGetComponentInstanceMethod(String name, String desc) {
return (name.startsWith("getComponentInstance")
&& Type.getType("Lorg/apache/felix/ipojo/ComponentInstance;").equals(Type.getReturnType(desc)));
}
private static boolean isManipulatedMethod(String name) {
return (name.startsWith(ClassManipulator.PREFIX));
}
/**
* Get collected interfaces.
*
* @return the interfaces implemented by the component class.
*/
public List<String> getInterfaces() {
return m_itfs;
}
/**
* Get collected fields.
*
* @return the field map [field_name, type].
*/
public Map<String, String> getFields() {
return m_fields;
}
/**
* Get collected methods.
*
* @return the method list of [method, signature].
*/
public List<MethodDescriptor> getMethods() {
return m_methods;
}
public String getSuperClass() {
return m_superClass;
}
public Collection<String> getInnerClasses() {
return m_inners.keySet();
}
public Map<String, List<MethodDescriptor>> getInnerClassesAndMethods() {
return m_inners;
}
public String getClassName() {
return m_className;
}
/**
* This class collects annotations in a method.
* This class creates an {@link AnnotationDescriptor}
* if an annotation is found during the visit.
* It also collects local variables definition.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
private final class MethodInfoCollector extends MethodVisitor {
/**
* The method descriptor of the visited method.
*/
private MethodDescriptor m_method;
/**
* Creates an annotation collector.
*
* @param md the method descriptor of the visited method.
*/
private MethodInfoCollector(MethodDescriptor md) {
super(Opcodes.ASM5);
m_method = md;
}
/**
* Visits an annotation.
* This class checks the visibility. If the annotation is visible,
* creates the {@link AnnotationDescriptor} corresponding to this annotation
* to visit this annotation. This {@link AnnotationDescriptor} is added to
* the {@link MethodDescriptor} of the visited method.
*
* @param name the name of the annotation
* @param visible is the annotation visible at runtime
* @return the {@link AnnotationDescriptor} to visit this annotation or
* <code>null</code> if the annotation is not visible.
* @see org.objectweb.asm.MethodVisitor#visitAnnotation(java.lang.String, boolean)
*/
public AnnotationVisitor visitAnnotation(String name, boolean visible) {
if (visible) {
AnnotationDescriptor ann = new AnnotationDescriptor(name, true);
m_method.addAnnotation(ann);
return ann;
}
return null;
}
public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
m_method.addLocalVariable(name, desc, signature, index);
}
public void visitEnd() {
m_method.end();
}
public AnnotationVisitor visitParameterAnnotation(int id,
String name, boolean visible) {
if (visible) {
AnnotationDescriptor ann = new AnnotationDescriptor(name, true);
m_method.addParameterAnnotation(id, ann);
return ann;
}
/*
* It is harmless to keep injected parameter annotations on original constructor
* for correct property resolution in case of re-manipulation
*/
if (m_method.getName().equals("$init")) {
AnnotationDescriptor ann = new AnnotationDescriptor(name, false);
m_method.addParameterAnnotation(id, ann);
return ann;
}
return null;
}
}
/**
* Describes a method or constructor annotation.
* This allows creating a copy of the annotations found in the original class
* to move them on inserted method. This class implements an
* {@link AnnotationVisitor} in order to create the copy.
* This class contains a <code>visit</code> method re-injecting the
* annotation in the generated method.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class AnnotationDescriptor extends AnnotationVisitor {
/**
* The name of the annotation.
*/
private String m_name;
/**
* Is the annotation visible at runtime?
*/
private boolean m_visible;
/**
* The description of the annotation.
* This attribute is set only for nested annotations.
*/
private String m_desc;
/**
* The list of 'simple' attributes.
*/
private List<SimpleAttribute> m_simples = new ArrayList<SimpleAttribute>(0);
/**
* The list of attribute containing an
* enumeration value.
*/
private List<EnumAttribute> m_enums = new ArrayList<EnumAttribute>(0);
/**
* The list of attribute which are
* annotations.
*/
private List<AnnotationDescriptor> m_nested = new ArrayList<AnnotationDescriptor>(0);
/**
* The list of attribute which are
* arrays.
*/
private List<ArrayAttribute> m_arrays = new ArrayList<ArrayAttribute>(0);
/**
* Creates an annotation descriptor.
* This constructor is used for 'root' annotations.
*
* @param name the name of the annotation
* @param visible the visibility of the annotation at runtime
*/
public AnnotationDescriptor(String name, boolean visible) {
super(Opcodes.ASM5);
m_name = name;
m_visible = visible;
}
/**
* Creates an annotation descriptor.
* This constructor is used for nested annotations.
*
* @param name the name of the annotation
* @param desc the descriptor of the annotation
*/
public AnnotationDescriptor(String name, String desc) {
super(Opcodes.ASM5);
m_name = name;
m_visible = true;
m_desc = desc;
}
/**
* Visits a simple attribute.
*
* @param arg0 the attribute name
* @param arg1 the attribute value
* @see org.objectweb.asm.AnnotationVisitor#visit(java.lang.String, java.lang.Object)
*/
public void visit(String arg0, Object arg1) {
m_simples.add(new SimpleAttribute(arg0, arg1));
}
/**
* Visits a nested annotation.
*
* @param arg0 the attribute name
* @param arg1 the annotation descriptor
* @return the annotation visitor parsing the nested annotation
* @see org.objectweb.asm.AnnotationVisitor#visitAnnotation(java.lang.String, java.lang.String)
*/
public AnnotationVisitor visitAnnotation(String arg0, String arg1) {
AnnotationDescriptor ad = new AnnotationDescriptor(arg0, arg1);
m_nested.add(ad);
return ad;
}
/**
* Visits an array attribute.
*
* @param arg0 the name of the attribute
* @return the annotation visitor parsing the content of the array,
* uses a specific {@link ArrayAttribute} to parse this array
* @see org.objectweb.asm.AnnotationVisitor#visitArray(java.lang.String)
*/
public AnnotationVisitor visitArray(String arg0) {
ArrayAttribute aa = new ArrayAttribute(arg0);
m_arrays.add(aa);
return aa;
}
/**
* End of the visit.
*
* @see org.objectweb.asm.AnnotationVisitor#visitEnd()
*/
public void visitEnd() {
}
/**
* Visits an enumeration attribute.
*
* @param arg0 the attribute name
* @param arg1 the enumeration descriptor
* @param arg2 the attribute value
* @see org.objectweb.asm.AnnotationVisitor#visitEnum(java.lang.String, java.lang.String, java.lang.String)
*/
public void visitEnum(String arg0, String arg1, String arg2) {
m_enums.add(new EnumAttribute(arg0, arg1, arg2));
}
/**
* Methods allowing to recreate the visited (stored) annotation
* into the destination method.
* This method recreate the annotations itself and any other
* attributes.
*
* @param mv the method visitor visiting the destination method.
*/
public void visitAnnotation(MethodVisitor mv) {
AnnotationVisitor av = mv.visitAnnotation(m_name, m_visible);
for (SimpleAttribute simple : m_simples) {
simple.visit(av);
}
for (EnumAttribute en : m_enums) {
en.visit(av);
}
for (AnnotationDescriptor nested : m_nested) {
nested.visit(av);
}
for (ArrayAttribute array : m_arrays) {
array.visit(av);
}
av.visitEnd();
}
/**
* Methods allowing to recreate the visited (stored) parameter annotations
* into the destination method.
* This method recreate the annotations itself and any other
* attributes.
*
* @param id the paramter id
* @param mv the method visitor visiting the destination method.
*/
public void visitParameterAnnotation(int id, MethodVisitor mv) {
AnnotationVisitor av = mv.visitParameterAnnotation(id, m_name, m_visible);
for (SimpleAttribute simple : m_simples) {
simple.visit(av);
}
for (EnumAttribute en : m_enums) {
en.visit(av);
}
for (AnnotationDescriptor nested : m_nested) {
nested.visit(av);
}
for (ArrayAttribute array : m_arrays) {
array.visit(av);
}
av.visitEnd();
}
/**
* Method allowing to recreate the visited (stored) annotation
* into the destination annotation. This method is used only
* for nested annotation.
*
* @param mv the annotation visitor to populate with the stored
* annotation
*/
public void visit(AnnotationVisitor mv) {
AnnotationVisitor av = mv.visitAnnotation(m_name, m_desc);
for (SimpleAttribute simple : m_simples) {
simple.visit(av);
}
for (EnumAttribute enu : m_enums) {
enu.visit(av);
}
for (AnnotationDescriptor nested : m_nested) {
nested.visit(av);
}
for (ArrayAttribute array : m_arrays) {
array.visit(av);
}
av.visitEnd();
}
}
/**
* Describes an array attribute.
* This class is able to visit an annotation array attribute, and to
* recreate this array on another annotation.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class ArrayAttribute extends AnnotationVisitor {
/**
* The name of the attribute.
*/
private String m_name;
/**
* The content of the parsed array.
*/
private List<Object> m_content = new ArrayList<Object>();
/**
* Creates an array attribute.
*
* @param name the name of the attribute.
*/
public ArrayAttribute(String name) {
super(Opcodes.ASM5);
m_name = name;
}
/**
* Visits the content of the array. This method is called for
* simple values.
*
* @param arg0 <code>null</code>
* @param arg1 the value
* @see org.objectweb.asm.AnnotationVisitor#visit(java.lang.String, java.lang.Object)
*/
public void visit(String arg0, Object arg1) {
m_content.add(arg1);
}
/**
* Visits the content of the array. This method is called for
* nested annotations (annotations contained in the array).
*
* @param arg0 <code>null</code>
* @param arg1 the annotation descriptor
* @return an {@link AnnotationDescriptor} which creates a copy of
* the contained annotation.
* @see org.objectweb.asm.AnnotationVisitor#visitAnnotation(String, String)
*/
public AnnotationVisitor visitAnnotation(String arg0, String arg1) {
AnnotationDescriptor ad = new AnnotationDescriptor(null, arg1);
m_content.add(ad);
return ad;
}
/**
* Visits the content of the array. This method is called for
* nested arrays (arrays contained in the array).
*
* @param arg0 <code>null</code>
* @return an {@link AnnotationVisitor} which creates a copy of
* the contained array.
* @see org.objectweb.asm.AnnotationVisitor#visitArray(String)
*/
public AnnotationVisitor visitArray(String arg0) {
ArrayAttribute aa = new ArrayAttribute(null);
m_content.add(aa);
return aa;
}
/**
* End of the array attribute visit.
*
* @see org.objectweb.asm.AnnotationVisitor#visitEnd()
*/
public void visitEnd() {
}
/**
* Visits the content of the array. This method is called for
* enumeration values.
*
* @param arg0 <code>null</code>
* @param arg1 the enumeration descriptor
* @param arg2 the value
* @see org.objectweb.asm.AnnotationVisitor#visitEnum(String, String, String)
*/
public void visitEnum(String arg0, String arg1, String arg2) {
EnumAttribute ea = new EnumAttribute(null, arg1, arg2);
m_content.add(ea);
}
/**
* Recreates the visited array attribute. This method
* handle the generation of the object embedded in the
* array.
*
* @param av the annotation visitor on which the array attribute
* needs to be injected.
*/
public void visit(AnnotationVisitor av) {
AnnotationVisitor content = av.visitArray(m_name);
for (Object component : m_content) {
if (component instanceof AnnotationDescriptor) {
((AnnotationDescriptor) component).visit(content);
} else if (component instanceof EnumAttribute) {
((EnumAttribute) component).visit(content);
} else if (component instanceof ArrayAttribute) {
((ArrayAttribute) component).visit(content);
} else { // Simple
content.visit(null, component);
}
}
content.visitEnd();
}
}
/**
* Describes a simple attribute.
* This class is able to visit an annotation simple attribute, and to
* recreate this attribute on another annotation.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public static final class SimpleAttribute {
/**
* The name of the attribute.
*/
private String m_name;
/**
* The value of the attribute.
*/
private Object m_value;
/**
* Creates a simple attribute.
*
* @param name the name of the attribute
* @param object the value of the attribute
*/
private SimpleAttribute(String name, Object object) {
m_name = name;
m_value = object;
}
/**
* Recreates the attribute on the given annotation.
*
* @param visitor the visitor on which the attribute needs
* to be injected.
*/
public void visit(AnnotationVisitor visitor) {
visitor.visit(m_name, m_value);
}
}
/**
* Describes an attribute. The value of this attribute is an enumerated
* value.
* This class is able to visit an annotation enumeration attribute, and to
* recreate this attribute on another annotation.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public static final class EnumAttribute {
/**
* The name of the attribute.
*/
private String m_name;
/**
* The descriptor of the enumeration.
*/
private String m_desc;
/**
* The value of the attribute.
*/
private String m_value;
/**
* Creates a enumeration attribute.
*
* @param name the name of the attribute.
* @param desc the descriptor of the {@link Enum}
* @param value the enumerated value
*/
private EnumAttribute(String name, String desc, String value) {
m_name = name;
m_value = value;
m_desc = desc;
}
/**
* Recreates the attribute on the given annotation.
*
* @param visitor the visitor on which the attribute needs
* to be injected.
*/
public void visit(AnnotationVisitor visitor) {
visitor.visitEnum(m_name, m_desc, m_value);
}
}
/**
* Class required to detect inner classes assigned to static field and thus must not be manipulated (FELIX-4347).
* If an inner class is assigned to a static field, it must not be manipulated.
* <p/>
* However notice that this is only useful when AspectJ is used, because aspectJ is changing the 'staticity' of
* the inner class.
*/
private class InnerClassAssignedToStaticFieldDetector extends MethodVisitor {
public InnerClassAssignedToStaticFieldDetector() {
super(Opcodes.ASM5);
}
@Override
public void visitTypeInsn(int opcode, String type) {
if (opcode == NEW && m_inners.containsKey(type)) {
m_inners.remove(type);
}
}
}
}