Classworking工具箱:分析泛型数据结构

应用类型替换深入挖掘使用泛型的程序的细节

简介:Java™ 5 泛型把详细的类型信息编码到类文件中。许多类型的工具都可以从全面的类型 信息提供的改进的数据结构中受益,但是要把这个信息变成有用的形式可能有些困难。为了让这个工作更 容易些,系列作者 Dennis Sosnoski 围绕 ASM Java 字节码操纵框架构建了一个数据结构分析程序,可 以解释泛型信息,为应用程序使用的数据的实际数据类型创建深度的视图。

类处理工具实际上就是一个把其他程序当成数据的程序,通常会修改或重新塑造目标程序,以满足某 些目的。在把程序当成数据的时候,如果构建一个关于程序自身内部数据结构的模型,协助对修改进行指 导,那么通常是有用的。可以利用反射,在第一次把目标程序的类文件装入 JVM 之后,创建这种类型的 模型。也可以用框架直接从类文件解码出数据结构信息,甚至从源代码进行解码。不论采用何种技术,目 的都是得到应用程序使用的对象之间关系的尽可能全面的视图。

Java 5 程序中的泛型信息,提供了应用程序数据结构的详细地图。泛型之前的程序,只要运行到 Java 集合类或使用无类型引用的应用程序类时,数据结构的分析就走进了死胡同。如果没有某种形式的 外部信息,就没有办法知道无类型引用链接到什么类型的对象。在使用泛型时,可以提供附加信息作为源 代码的一部分,然后编译器会把附加的引用类型信息直接整合到二进制的类文件中。利用这种内嵌的泛型 信息是建立对象之间关系的更丰富视图的关键所在。

在这个系列的前两篇文章(“反射泛型” 和 “泛型与 ASM”)中,我首先介绍了使用反射得到泛型 信息的基础知识,然后对于使用 ASM 字节码框架处理类文件的原始泛型信息作了详细介绍。在这篇文章 中,我把 ASM 技术用得更深入一点儿,在泛型类定义中使用类型替换来构建目标应用程序数据结构的增 强视图。在进入分析类所使用的实际 ASM 代码之前,我先介绍表示和组织数据结构信息时一些比较简单 的问题。

表示数据结构

在构建分析程序时的第一个问题是,定义目标程序使用的数据结构的表示形式。这必须不仅包含每个 字段值的表示,还要包含每个值的类型信息的表示。因为我想在这一期中演示泛型的解码,所以类型信息 需要包含泛型引用所使用的具体的参数类型。

清单 1 显示了我用作基本数据结构表示的类。FieldDescription 类只是个简单的数据类,容纳字段 名称、签名和字段类型的引用。正如我在 前一期 中介绍的,签名是泛型添加到类文件格式中的项目;只 有对泛型的引用才有签名。签名定义了泛型实际使用的参数类型,所以它提供了处理类型替换时需要的信 息。对于没有签名的字段,将只使用 null 值。最后,字段类型都是 TypeDescription 类的实例,如清 单 1 所示:

清单 1. 基本数据结构类

public class FieldDescription{   // every field has a name   private final String m_name;   // only fields that are of generic types have signatures   private final String m_signature;   // type only defined when parameter types are defined   private final TypeDescription m_type;   public FieldDescription(String name, String sig, TypeDescription type) {     m_name = name;     m_signature = sig;     m_type = type;   }   public String getName() {     return m_name;   }   public String getSignature() {     return m_signature;   }   public TypeDescription getType() {     return m_type;   }}public abstract class TypeDescription{   public static final FieldDescription[] EMPTY_FIELD_ARRAY = {};   private final String m_descripTor;   protected TypeDescription(String dTor) {     m_descripTor = dTor;   }   public boolean isArray() {     return false;   }   public TypeDescription getArrayItemType() {     throw new IllegalStateException("Not an array");   }   public boolean isPrimitive() {     return false;   }   public FieldDescription[] getFields() {     return EMPTY_FIELD_ARRAY;   }   public String getDescripTor() {     return m_descripTor;   }   public boolean equals(Object obj) {     if (obj == this) {       return true;     } else if (obj instanceof TypeDescription) {       return m_descripTor.equals(((TypeDescription)obj).m_descripTor);     } else {       return false;     }   }   public int hashCode() {     return m_descripTor.hashCode();   }   public abstract String toString();}

TypeDescription 类只是个抽象基类,其中定义了处理三种类型的方法:原生类型、数组和类实例。 这个基类以类型描述符的形式包含了一个值。我用于这个类的类型描述符大致符合 JVM 规范定义的类型 描述符的形式,但是有些扩展,添加了泛型具体 “版本” 的实际参数类型列表。这个扩展允许把 Ljava/util/Map; 这种形式的描述符作为类型系统的一 部分。

组织数据结构

组织数据结构信息的问题比表示信息的问题略微复杂一些。组织信息的基本问题在于数据结构经常是 递归的:一个类中可能有一个字段,这个字段引用这个类的一个实例,或者它引用的一个类可能直接或间 接地引用回到原来的类。所以,只想直接展开结构,可能会形成无限的递归循环。

我使用目录技术来处理这种递归引用。在找到类引用时,检查目标,查看这个类以前是否已经看到过 。如果是,那么目录就返回现有的描述。如果没有,则目录创建新的描述实例,并立即把描述添加到目录 (甚至在描述的完整细节可用之前就添加)。清单 2 显示了这个 TypeDirecTory 类的基本代码,现在暂 时不管它,以后会添加处理泛型的一些细节:

清单 2. TypeDirecTory 类

public class TypeDirecTory{   /** Source for binary class representations. */   private final BinaryClassLoader m_loader;   /** Field list for all arrays. */   private final FieldDescription[] m_arrayFields;   /** Map from descripTor or signature to type for all non-generic  types,    * including generics with substitutions. */   private HashMap m_typeMap =     new HashMap();   ...   /**    * ConstrucTor. Initializes the type direcTory with descriptions of    * primitive types.    *    * @param loader binary class loader    */   public TypeDirecTory(BinaryClassLoader loader) {     m_loader = loader;     addType(new PrimitiveTypeDescription("B", "byte"));     addType(new PrimitiveTypeDescription("C", "char"));     addType(new PrimitiveTypeDescription("D", "double"));     addType(new PrimitiveTypeDescription("F", "float"));     TypeDescription inttype = new PrimitiveTypeDescription("I", "int");     addType(inttype);     addType(new PrimitiveTypeDescription("J", "long"));     addType(new PrimitiveTypeDescription("S", "short"));     addType(new PrimitiveTypeDescription("V", "void"));     addType(new PrimitiveTypeDescription("Z", "boolean"));     m_arrayFields = new FieldDescription[] {       new FieldDescription("int", null, inttype)     };   }   /**    * Add type description to type direcTory.    *    * @param desc type description to add    */   public void addType(TypeDescription desc) {     m_typeMap.put(desc.getDescripTor(), desc);   }   /**    * Add generic class to template direcTory.    *    * @param tmpl generic template to add    */   public void addTemplate(GenericTemplate tmpl) {     m_templateMap.put(tmpl.getDescripTor(), tmpl);   }   /**    * Get description for the type. The type may be a primitive, an array,  or a    * class. If the type is a generic class, it will be treated as though  all    * type variables used their lower bounds.    *    * @param dTor type descripTor    * @return type description    */   public TypeDescription getTypeInstance(String dTor) {     // check for an existing description     TypeDescription desc = m_typeMap.get(dTor);     if (desc == null) {       // new description needed - must be array or class       if (dTor.charAt(0) == '[') {         desc = new ArrayClassDescripTor(dTor,           getTypeInstance(dTor.substring(1)));       } else {         // parse binary class to build description         byte[] byts = m_loader.getBytes(dTor);         desc = new SimpleClassDescription(dTor, byts, this);       }     }     return desc;   }   /**    * Get description for generic class with specific type substitutions.    *    * @param sig field signature with type substitutions    * @param types actual types used for instance (values may be    * null if no substitution defined)    * @return type description    */   public TypeDescription getSignatureInstance(String sig,     TypeDescription[] types) {     // first check for direct match on substituted signature     TypeDescription desc = (TypeDescription)m_typeMap.get(sig);     if (desc == null) {       // no direct match, first handle array       if (sig.charAt(0) == '[') {         desc = new ArrayClassDescripTor(sig,           getSignatureInstance(sig.substring(1), types));       } else {         ...       }     }     return desc;   }   /**    * Get description for signature with type mapping.    *    * @param sig field signature for type variables    * @param tmap type mapping for variables    * @return type description    */   public TypeDescription getMappedSignatureInstance(String sig,     HashMap tmap) {     ...   }   ...   /**    * DescripTor for primitive "class." There's really nothing to record for  a    * primitive type except the name, so that's all this does. Because  instances    * only need to be created and added to the direcTory at initialization,  this    * is an inner class.    */   private static class PrimitiveTypeDescription extends TypeDescription   {     private final String m_externalName;     private PrimitiveTypeDescription(String iname, String xname) {       super(iname);       m_externalName = xname;     }     public boolean isPrimitive() {       return true;     }     public String toString() {       return m_externalName;     }   }   /**    * DescripTor for array class. This is just a wrapper around the  descripTor    * for the item type, with the same field list used for all arrays.    */   private class ArrayClassDescripTor extends TypeDescription   {     private final TypeDescription m_itemType;     protected ArrayClassDescripTor(String name, TypeDescription item) {       super(name);       m_itemType = item;     }     public boolean isArray() {       return true;     }     public TypeDescription getArrayItemType() {       return m_itemType;     }     public FieldDescription[] getFields() {       return m_arrayFields;     }     public String toString() {       return m_itemType + "[]";     }   }}

清单 2 的代码使用内部的 PrimitiveTypeDescription 和 ArrayTypeDescription 类直接处理原生类 型和数组类型。原生类型在创建的时候就被添加到实际的目录(m_typeMap 哈希映射把类型名称或字段签 名与对应的类型描述关联起来),而数组类型则在被看到的时候添加到目录中。getTypeInstance() 方法 处理新的数组类型的添加,递归地调用本身来得到数组的项目类型的类型描述。实际的 Java 类的类型则 通过把二进制类表示传递给 SimpleClassDescription 构造函数而生成。

getSignatureInstance() 方法在结构上与 getTypeInstance() 方法很类似,但是如果在前面没有遇 到使用某个泛型类的参数类型的相同组合,它还需要处理到泛型类的类型替换。马上我就要编写实际执行 这个任务的代码,以及相关的 getMappedSignatureInstance() 代码。首先,我要处理的代码负责构建不 带指定参数类型的类的描述。

用 ASM 构建类描述

清单 3 显示了简单类描述的实际构造,所谓简单类,我指的是非泛型类或者不带指定参数类型的泛型 类。这个类的全部工作都发生在构造函数中,构造函数首先把类的新实例添加到类型目录(以处理递归, 就像在 前一节 中讨论的),然后用 ASM 处理实际的二进制类表示,构建一组字段描述。

清单 3. SimpleClassDescription 类

public class SimpleClassDescription extends TypeDescription{   private final String m_name;   private final FieldDescription[] m_fields;   public SimpleClassDescription(String dTor, byte[] byts, TypeDirecTory dir)  {     super(dTor);     m_name = BinaryClassLoader.nameFromDescripTor(dTor);     dir.addType(this);     DescriptionBuilderVisiTor vTor = new DescriptionBuilderVisiTor(dir);     ClassReader creader = new ClassReader(byts);     creader.accept(vTor, true);     m_fields = vTor.getFields();   }   public FieldDescription[] getFields() {     return m_fields;   }   public String toString() {     return m_name;   }   /**    * VisiTor for generating the description information for a simple class  (a    * non-generic class, or a generic class used without type  substitution).    * If the class is generic, the bounds information from the signature  is    * substituted for the type parameters.    */   public class DescriptionBuilderVisiTor extends EmptyVisiTor   {     private final TypeDirecTory m_typeDirecTory;     private HashMap m_typeMap;     private ArrayList m_fields;     private DescriptionBuilderVisiTor(TypeDirecTory dir) {       m_typeDirecTory = dir;       m_fields = new ArrayList();     }     public void visit(int version, int Access, String name, String sig,       String sname, String[] inames) {       if (sig != null) {         m_typeMap = new HashMap();         new SignatureReader(sig).accept(new ClassSignatureVisiTor());       }     }     public FieldVisiTor visitField(int Access, String name, String desc,       String sig, Object value) {       TypeDescription type;       if (sig == null) {         type = m_typeDirecTory.getTypeInstance(desc);       } else {         type = m_typeDirecTory.getMappedSignatureInstance(sig, m_typeMap);       }       m_fields.add(new FieldDescription(name, sig, type));       return super.visitField(Access, name, desc, sig, value);     }     private FieldDescription[] getFields() {       return m_fields.toArray(new FieldDescription[m_fields.size()]);     }     private class ClassSignatureVisiTor extends EmptySignatureVisiTor     {       private String m_lastName;       private boolean m_isBounded;       public void visitFormalTypeParameter(String name) {         m_typeMap.put(name,           m_typeDirecTory.getTypeInstance("Ljava/lang/Object;"));         m_lastName = name;       }       public SignatureVisiTor visitClassBound() {         return new EmptySignatureVisiTor() {           public void visitClassType(String name) {             m_typeMap.put(m_lastName,               m_typeDirecTory.getTypeInstance("L" + name + ';'));             m_isBounded = true;           }         };       }       public SignatureVisiTor visitInterfaceBound() {         return new EmptySignatureVisiTor() {           public void visitClassType(String name) {             if (!m_isBounded) {               m_typeMap.put(m_lastName,                 m_typeDirecTory.getTypeInstance(name));             }           }         };       }     }   }}

清单 3 代码中有意思的部分在于内部的 DescriptionBuilderVisiTor 类。这个类是 ASM 的 EmptyVisiTor 类的扩展,只覆盖了两个方法。覆盖的 visit() 方法处理实际的类信息,创建嵌套内部类 ClassSignatureVisiTor 的实例,处理有签名的类(即泛型类)。这个嵌套内部类的要点就在于扫描泛型 类的类型参数,找到每个泛型类的最佳上部绑定,并在包含它们的 DescriptionBuilderVisiTor 类实例 所拥有的哈希映射中设置这个绑定。DescriptionBuilderVisiTor 覆盖的 visitField() 方法只检查字段 有没有签名。如果字段没有签名,visitField() 就只用字段描述符字符串从类型目录获得类型信息。如 果字段确实 有签名,那么 visitField() 就使用类型目录查找的备选形式(接受签名和替换类型的哈希 映射)。

DescriptionBuilderVisiTor 类代码的实际效果是构建一组字段定义,与提供给 SimpleClassDescription 构造函数的类的字段匹配。如果提供的类是泛型类,那么即使为这个类提供了 最通用的参数类型定义,也会处理类的所有字段。对于类型参数定义的最简单形式,这个最通用的参数类 型就是 java.lang.Object;如果类型参数定义包含类或接口绑定(例如 public class MyClass),那么最通用的类型与指定绑定匹配。

处理类型替换

现在我们已经完成了最简单的处理部分,剩下的惟一部分是实际的类型替换处理。清单 4 显示了执行 这个处理的代码,包括 清单 2 所示的 TypeDirecTory 类中遗漏的部分和新的 GenericTemplate 类:

清单 4. 类型替换代码

public class TypeDirecTory{   ...   /** Map from generic classes descripTor to generic class information.  */   private HashMap m_templateMap =     new HashMap();   ...   /**    * Get description for generic class with specific type substitutions.    *    * @param sig field signature with type substitutions    * @param types actual types used for instance (values may be    * null if no substitution defined)    * @return type description    */   public TypeDescription getSignatureInstance(String sig,     TypeDescription[] types) {     // first check for direct match on substituted signature     TypeDescription desc = (TypeDescription)m_typeMap.get(sig);     if (desc == null) {       // no direct match, first handle array       if (sig.charAt(0) == '[') {         desc = new ArrayClassDescripTor(sig,           getSignatureInstance(sig.substring(1), types));       } else {         // not an array, get the generic class descripTor         String dTor = sig;         int split = dTor.indexOf('= 0) {           dTor = dTor.substring(0, split) + ';';         }         GenericTemplate gdesc = m_templateMap.get(dTor);         if (gdesc == null) {           byte[] byts = m_loader.getBytes(dTor);           gdesc = new GenericTemplate(dTor, byts, this);         }         // handle type substitution to generic version of class         desc = gdesc.getParameterized(sig, types);       }     }     return desc;   }   /**    * Get description for signature with type mapping.    *    * @param sig field signature for type variables    * @param tmap type mapping for variables    * @return type description    */   public TypeDescription getMappedSignatureInstance(String sig,     HashMap tmap) {     SignatureDecompositionVisiTor vTor =       new SignatureDecompositionVisiTor(tmap);     new SignatureReader(sig).acceptType(vTor);     return vTor.getDescription();   }   /**    * VisiTor for processing a signature with type substitutions. This  uses    * itself recursively to process nested signatures, substituting actual    * types for any type variable references found.    */   public class SignatureDecompositionVisiTor extends EmptySignatureVisiTor   {     private final HashMap m_parameterMap;     private int m_arrayCount;     private String m_className;     private char m_baseClass;     private ArrayList m_parameterTypes;     private boolean m_isNested;     private SignatureDecompositionVisiTor m_nestedVisiTor;     private void reset() {       m_arrayCount = 0;       m_className = null;       m_parameterTypes.clear();       m_isNested = false;     }     private SignatureDecompositionVisiTor(HashMap  tmap) {       m_parameterMap = tmap;       m_parameterTypes = new ArrayList();     }     private void checkParameter() {       if (m_isNested) {         m_parameterTypes.add(m_nestedVisiTor.getDescription());         m_isNested = false;       }     }     public SignatureVisiTor visitArrayType() {       m_arrayCount++;       return this;     }     public void visitBaseType(char desc) {       m_baseClass = desc;     }     public void visitTypeVariable(String name) {       String dTor = m_parameterMap.get(name).getDescripTor();       if (dTor == null) {         throw new IllegalStateException("Undefined type variable " +  name);       } else if (dTor.length() < 3 || dTor.charAt(0) != 'L' ||         dTor.charAt(dTor.length()-1) != ';') {         throw new IllegalArgumentException           ("Not a valid class descripTor: " + dTor);       } else {         m_className = dTor.substring(1, dTor.length()-1);       }     }     public void visitClassType(String name) {       m_className = name;     }     public SignatureVisiTor visitTypeArgument(char wildcard) {       checkParameter();       if (wildcard == '=' || wildcard == '+') {         if (m_nestedVisiTor == null) {           m_nestedVisiTor = new SignatureDecompositionVisiTor (m_parameterMap);         } else {           m_nestedVisiTor.reset();         }         m_isNested = true;         return m_nestedVisiTor;       } else {         m_parameterTypes.add(null);         return new EmptySignatureVisiTor();       }     }     public void visitTypeArgument() {       checkParameter();       m_parameterTypes.add(null);     }     public void visitEnd() {       checkParameter();     }     public TypeDescription getDescription() {       // create array signature prefix       StringBuffer buff = new StringBuffer();       for (int i = 0; i  0) {           buff.append('<');           for (int i = 0; i ');         }         buff.append(';');         // get actual class description         if (m_parameterTypes.size() == 0) {           return getTypeInstance(buff.toString());         } else {           TypeDescription[] ptypes =             new TypeDescription[m_parameterTypes.size()];           ptypes = m_parameterTypes.toArray(ptypes);           return getSignatureInstance(buff.toString(), ptypes);         }       }     }   }   ...}public class GenericTemplate{   private final String m_descripTor;   private final String m_baseName;   private final TypeDirecTory m_typeDirecTory;   private final FieldDescription[] m_genericFields;   private final String[] m_typeParameters;   private final TypeDescription[] m_upperBounds;   protected GenericTemplate(String dTor, byte[] byts, TypeDirecTory dir) {     m_descripTor = dTor;     m_baseName = BinaryClassLoader.nameFromDescripTor(dTor);     m_typeDirecTory = dir;     dir.addTemplate(this);     DescriptionBuilderVisiTor vTor = new DescriptionBuilderVisiTor(dir);     ClassReader creader = new ClassReader(byts);     creader.accept(vTor, true);     m_genericFields = vTor.getFields();     m_typeParameters = vTor.getTypeParameters();     m_upperBounds = vTor.getUpperBounds();   }   public String getDescripTor() {     return m_descripTor;   }   public boolean equals(Object obj) {     if (obj == this) {       return true;     } else if (obj instanceof GenericTemplate) {       return m_descripTor.equals(((GenericTemplate)obj).m_descripTor);     } else {       return false;     }   }   public int hashCode() {     return m_descripTor.hashCode();   }   /**    * Get description for parameterized type with type substitutions.    *    * @param sig signature including all type substitutions    * @param types actual types used for instance (values may be    * null if no substitution defined)    * @param tdir type direcTory    * @return substituted type description    */   public TypeDescription getParameterized(String sig, TypeDescription[] types)  {     return new ParameterizedClassDescription(sig, types);   }   /**    * VisiTor for generating the description information for a generic  class.    */   public class DescriptionBuilderVisiTor extends EmptyVisiTor   {     private final TypeDirecTory m_typeDirecTory;     private ArrayList m_fields;     private ArrayList m_typeParameters;     private ArrayList m_upperBounds;     private DescriptionBuilderVisiTor(TypeDirecTory dir) {       m_typeDirecTory = dir;       m_fields = new ArrayList();       m_typeParameters = new ArrayList();       m_upperBounds = new ArrayList();     }     public void visit(int version, int Access, String name, String sig,       String sname, String[] inames) {       if (sig != null) {         new SignatureReader(sig).accept(new ClassSignatureVisiTor());       }       super.visit(version, Access, name, sig, sname, inames);     }     public FieldVisiTor visitField(int Access, String name, String desc,       String sig, Object value) {       TypeDescription type = null;       if (sig == null) {         type = m_typeDirecTory.getTypeInstance(desc);       }       m_fields.add(new FieldDescription(name, sig, type));       return super.visitField(Access, name, desc, sig, value);     }     private FieldDescription[] getFields() {       return m_fields.toArray(new FieldDescription[m_fields.size()]);     }     private String[] getTypeParameters() {       return m_typeParameters.toArray(new String[m_typeParameters.size()]);     }     private TypeDescription[] getUpperBounds() {       return m_upperBounds.toArray(new TypeDescription[m_upperBounds.size ()]);     }     private class ClassSignatureVisiTor extends EmptySignatureVisiTor     {       private boolean m_isBounded;       public void visitFormalTypeParameter(String name) {         m_typeParameters.add(name);         m_upperBounds.add(m_typeDirecTory.getTypeInstance ("Ljava/lang/Object;"));       }       public SignatureVisiTor visitClassBound() {         return new EmptySignatureVisiTor() {           public void visitClassType(String name) {             m_upperBounds.set(m_upperBounds.size()-1,               m_typeDirecTory.getTypeInstance("L" + name + ';'));             m_isBounded = true;           }         };       }       public SignatureVisiTor visitInterfaceBound() {         return new EmptySignatureVisiTor() {           public void visitClassType(String name) {             if (!m_isBounded) {               m_upperBounds.set(m_upperBounds.size()-1,                 m_typeDirecTory.getTypeInstance(name));             }           }         };       }     }   }   /**    * Parameterized type description, with actual types substituted for  all    * type parameters. The actual substitution of type parameters into  the    * fields is done at the time of first use to avoid issues with     * recursive class references.    */   private class ParameterizedClassDescription extends TypeDescription   {     private final TypeDescription[] m_types;     private final String m_name;     private FieldDescription[] m_fields;     /**      * ConstrucTor. This creates the description for a parameterized  type      * with type substitutions from the generic instance. This has to  add      * itself to the type direcTory during construction to handle      * recursive references.      *      * @param sig signature including all type substitutions      * @param types actual types used for instance (values may be      * null if no substitution defined)      * @return substituted type description      */     public ParameterizedClassDescription(String sig, TypeDescription[] types)  {       super(sig);       StringBuffer buff = new StringBuffer();       buff.append(m_baseName);       buff.append('<');       for (int i = 0; i  0) {           buff.append(',');         }         if (types[i] == null) {           buff.append('*');         } else {           buff.append(types[i]);         }       }       buff.append('>');       m_name = buff.toString();       m_types = types;       m_typeDirecTory.addType(this);     }     public FieldDescription[] getFields() {       if (m_fields == null) {         // make sure the number of parameter types is correct         if (m_typeParameters.length != m_types.length) {           throw new IllegalArgumentException("Wrong number of            parameter types");         }         // build substitution map for type parameters         HashMap tmap =           new HashMap();         for (int i = 0; i < m_typeParameters.length; i++) {           TypeDescription type = m_types[i];           if (type == null) {             type = m_upperBounds[i];           }           tmap.put(m_typeParameters[i], type);         }         // handle the actual type substitutions         m_fields = new FieldDescription[m_genericFields.length];         for (int i = 0; i < m_genericFields.length; i++) {           FieldDescription field = m_genericFields[i];           if (field.getType() == null) {             String sig = field.getSignature();             TypeDescription desc =              m_typeDirecTory.getMappedSignatureInstance(sig, tmap);             m_fields[i] = new FieldDescription(field.getName(), sig,  desc);           } else {             m_fields[i] = field;           }         }       }       return m_fields;     }     public String toString() {       return m_name;     }   }}

TypeDirecTory 类中第一个遗漏的部分是 m_templateMap 字段。这个哈希映射把没有参数类型的泛型 类的字段描述符字符串与这个类的 GenericTemplate 关联起来。GenericTemplate 实例包含类的基本类 型参数信息,还有为类定义的字段的数组。不是泛型引用的字段都在泛型模型定义的时候完全定义;是 泛型引用的字段则保留作为签名,留在以后进行填充。内部的 DescriptionBuilderVisiTor 类实现从泛 型类的二进制表示实际收集这个模板信息的实际 ASM 处理工作。

清单 2 的 TypeDirecTory 类遗漏的其他部分是 getSignatureInstance() 和 getMappedSignatureInstance() 方法以及后一个方法使用的内部 SignatureDecompositionVisiTor 类的 完整代码。getSignatureInstance() 方法实际地构建新的泛型类模板,然后为参数类型的具体列表实例 化这些模板。实例化过程由 GenericTemplate 的内部 ParameterizedClassDescription 类的构造函数方 法设置,但是会推迟到这个 TypeDescription 子类第一次请求字段列表的时候。

为什么要在 ParameterizedClassDescription 类中推迟替换的处理?还是因为递归的问题。如果没有 延迟处理,就会发生这样的情况:仅仅对泛型模板的基本字段定义进行处理,就会引起对这个模板的同一 版本的实例化。

TypeDirecTory 类的 getMappedSignatureInstance() 方法接受字段签名和一个映射(映射中保存类 型参数变量名称到实际参数类型的映射),这个方法使用内部 SignatureDecompositionVisiTor 类的实 例来处理签名,并在看到类型参数变量的时候执行替换。签名完全处理之后, getMappedSignatureInstance() 方法的代码调用内部类实例的 getDescription() 方法,得到与传递进 来的签名和替换匹配的实际的类型描述。

试一下

现在已经看到了完整的数据结构分析引擎,所以剩下的就是显示它的实际功效了。为了这个目的,我 要使用在 前两篇文章 中开始的代表文件目录的同一套泛型类。因为在这篇文章中我只关注这些类的字段 而不是代码,所以在清单 5 中只显示了这些类的字段定义:

清单 5. 文件目录类

public class PathDirecTory implements Iterable{   private final PairCollection m_pathPairs;   ...}public class PairCollection implements  Iterable{   /** Collection with first component values. */   private final ArrayList m_tValues;   /** Collection with second component values. */   private final ArrayList m_uValues;   ...}public class DirInfo{   private final List m_files;   private final List m_direcTories;   private final Date m_lastModify;   ...}public class FileInfo{   private final String m_name;   private final Date m_lastModify;   ...}

仅仅在一个类上运行数据结构分析引擎代码,不那么有趣,因为结果只是代表另一个数据结构的数据 结构。为了生成一些有趣的输出,我编写了清单 6 中的 Datalyze 类。这个类首先分析目标类的数据结 构,然后递归地扫描目标类和所有引用的类,输出找到的类的描述信息。为了避免输出太杂乱,当碰到来 自 java.lang.* 或 sun.* 包层次结构的类时,就停止递归。清单 6 底部的输出显示了在 清单 4 中的 PathDirecTory 类上运行这个类的结果:

清单 6. 文件目录类

public abstract class Datalyze{   private static void printDescription(TypeDescription basetype) {     ArrayList refs = new ArrayList();     HashSet dones = new HashSet();     refs.add(basetype);     for (int i = 0; i < refs.size(); i++) {       TypeDescription ref = refs.get(i);       System.out.println(ref + " fields:");       FieldDescription[] fields = ref.getFields();       for (int j = 0; j  0 &&           !fdTor.startsWith("Ljava/lang") &&           !fdTor.startsWith("Lsun/")) {           refs.add(ftype);           dones.add(ftype);         }       }     }   }   public static void main(String[] args) {     BinaryClassLoader loader = new BinaryClassLoader();     loader.addPaths(System.getProperty("java.class.path"));     loader.addPaths(System.getProperty("sun.boot.class.path"));     TypeDirecTory tdir = new TypeDirecTory(loader);     TypeDescription desc =      tdir.getTypeInstance("Lcom/sosnoski/generics/PathDirecTory;");     printDescription(desc);   }}com.sosnoski.generics.PathDirecTory fields:  m_pathPairs of type  com.sosnoski.generics.PairCollectioncom.sosnoski.generics.PairCollection  fields:  m_tValues of type java.util.ArrayList  m_uValues of type java.util.ArrayListjava.util.ArrayList fields:  serialVersionUID of type long  elementData of type java.lang.String[]  size of type intjava.util.ArrayList fields:  serialVersionUID of type long  elementData of type com.sosnoski.generics.DirInfo[]  size of type intcom.sosnoski.generics.DirInfo fields:  m_files of type java.util.List  m_direcTories of type java.util.List  m_lastModify of type java.util.Datejava.util.Date fields:  gcal of type sun.util.calendar.BaseCalendar  jcal of type sun.util.calendar.BaseCalendar  fastTime of type long  cdate of type sun.util.calendar.BaseCalendar$Date  defaultCenturyStart of type int  serialVersionUID of type long  wtb of type java.lang.String[]  ttb of type int[]

在清单 6 底部的输出中(为了格式化稍微重新调整了结构),可以看到类型替换代码的作用。类型替 换的第一个实例在输出的第 4 和第 5 行,里面指定了 ArrayList 实例中值的类型。接下来的 ArrayList 分析显示出它有一个 String[] 字段。这个输出也显示了数据结构分析的局限 性:一旦遇到 DirInfo 类中的 List 字段,就没法进一步分解了,因为 java.util.List 是接口而不是 实际的类 —— 而且只有接口的实现才有字段。当然,对本文提供的代码做些修改,也可以把同样的分析 像用于字段一样用于方法,而且这种分析方法也能包含接口。

泛型结束语

到现在,已经看到了如何用 ASM 框架实现泛型数据结构分析,还有一个快速的示例,表现了这类分析 的可能性和局限性。还有一些比较好的应用,通过在工具中整合泛型,在 Java 类之间和一些外部形式之 间进行转换,从而提供详细的数据视图。例如,我正计划在我的 JiBX 数据绑定框架的新版本绑定生成器 中,利用泛型提供的信息,在不需要用户的额外输入的情况下,填充结构的更多细节。

在三篇关于泛型的文章之后,我要换一个主题了!接下来的类处理工具包 系列中,我将介绍一个处理 Java 5 标注的工具。以前,我曾经介绍过在使用标注作为配置信息的时候出现的问题。这个工具通过允 许在运行时覆盖标注数据,试图克服这些问题。覆盖标注的能力,能不能让它们用于更多类型的应用?请 关注下个月的文章来找到答案。

本文配套源码:http://www.bianceng.net/java/201212/733.htm

让所有的愁向后飞去。请不要回头去追你因该向前奔跑,因为快乐在前方!

Classworking工具箱:分析泛型数据结构

相关文章:

你感兴趣的文章:

标签云: