程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Classworking工具箱: 分析泛型數據結構

Classworking工具箱: 分析泛型數據結構

編輯:關於JAVA

應用類型替換深入挖掘使用泛型的程序的細節

簡介: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<Ljava/lang/String;Ljava/lang/String;>; 這種形式的描述符作為類型系統的一 部分。

組織數據結構

組織數據結構信息的問題比表示信息的問題略微復雜一些。組織信息的基本問題在於數據結構經常是 遞歸的:一個類中可能有一個字段,這個字段引用這個類的一個實例,或者它引用的一個類可能直接或間 接地引用回到原來的類。所以,只想直接展開結構,可能會形成無限的遞歸循環。

我使用目錄技術來處理這種遞歸引用。在找到類引用時,檢查目標,查看這個類以前是否已經看到過 。如果是,那麼目錄就返回現有的描述。如果沒有,則目錄創建新的描述實例,並立即把描述添加到目錄 (甚至在描述的完整細節可用之前就添加)。清單 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<String,TypeDescription> m_typeMap =
     new HashMap<String,TypeDescription>();
   ...

   /**
    * 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
    * <code>null</code> 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<String,TypeDescription> 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<String,TypeDescription> m_typeMap;
     private ArrayList<FieldDescription> m_fields;

     private DescriptionBuilderVisitor(TypeDirectory dir) {
       m_typeDirectory = dir;
       m_fields = new ArrayList<FieldDescription>();
     }

     public void visit(int version, int access, String name, String sig,
       String sname, String[] inames) {
       if (sig != null) {
         m_typeMap = new HashMap<String,TypeDescription>();
         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<T implements java.util.List>),那麼最通用的類型與指定綁定匹配。

處理類型替換

現在我們已經完成了最簡單的處理部分,剩下的惟一部分是實際的類型替換處理。清單 4 顯示了執行 這個處理的代碼,包括 清單 2 所示的 TypeDirectory 類中遺漏的部分和新的 GenericTemplate 類:

清單 4. 類型替換代碼

public class TypeDirectory
{
   ...
   /** Map from generic classes descriptor to generic class information.  */
   private HashMap<String,GenericTemplate> m_templateMap =
     new HashMap<String,GenericTemplate>();
   ...

   /**
    * 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
    * <code>null</code> 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('<');
         if (split >= 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<String,TypeDescription> 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<String,TypeDescription> m_parameterMap;
     private int m_arrayCount;
     private String m_className;
     private char m_baseClass;
     private ArrayList<TypeDescription> 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<String,TypeDescription>  tmap) {
       m_parameterMap = tmap;
       m_parameterTypes = new ArrayList<TypeDescription>();
     }

     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 < m_arrayCount; i++) {
         buff.append('[');
       }

       // get the actual type description
       if (m_className == null) {
         buff.append(m_baseClass);
         return getTypeInstance(buff.toString());
       } else {

         // construct both descriptor and full signature for type
         buff.append('L');
         buff.append(m_className);
         if (m_parameterTypes.size() > 0) {
           buff.append('<');
           for (int i = 0; i < m_parameterTypes.size(); i++) {
             TypeDescription pdesc = m_parameterTypes.get(i);
             if (pdesc == null) {
               buff.append('*');
             } else {
               buff.append(pdesc.getDescriptor());
             }
           }
           buff.append('>');
         }
         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
    * <code>null</code> 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<FieldDescription> m_fields;
     private ArrayList<String> m_typeParameters;
     private ArrayList<TypeDescription> m_upperBounds;

     private DescriptionBuilderVisitor(TypeDirectory dir) {
       m_typeDirectory = dir;
       m_fields = new ArrayList<FieldDescription>();
       m_typeParameters = new ArrayList<String>();
       m_upperBounds = new ArrayList<TypeDescription>();
     }

     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
      * <code>null</code> 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 < types.length; i++) {
         if (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<String,TypeDescription> tmap =
           new HashMap<String,TypeDescription>();
         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<String>
{
   private final PairCollection<String, DirInfo> m_pathPairs;
   ...
}

public class PairCollection<T,U extends DirInfo> implements  Iterable<T>
{
   /** Collection with first component values. */
   private final ArrayList<T> m_tValues;

   /** Collection with second component values. */
   private final ArrayList<U> m_uValues;
   ...
}

public class DirInfo
{
   private final List<FileInfo> m_files;
   private final List<DirInfo> 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<TypeDescription> refs = new ArrayList<TypeDescription>();
     HashSet<TypeDescription> dones = new HashSet<TypeDescription>();
     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 < fields.length; j++) {
         FieldDescription field = fields[j];
         TypeDescription ftype = field.getType();
         System.out.println(" " + field.getName() + " of type " + ftype);
         while (ftype.isArray()) {
           ftype = ftype.getArrayItemType();
         }
         String fdtor = ftype.getDescriptor();
         if (!dones.contains(ftype) && !ftype.isPrimitive() &&
           ftype.getFields().length > 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.PairCollection<java.lang.String,com.sosnoski.generics.DirInfo>
com.sosnoski.generics.PairCollection<java.lang.String,com.sosnoski.generics.DirInfo>
  fields:
  m_tValues of type java.util.ArrayList<java.lang.String>
  m_uValues of type java.util.ArrayList<com.sosnoski.generics.DirInfo>
java.util.ArrayList<java.lang.String> fields:
  serialVersionUID of type long
  elementData of type java.lang.String[]
  size of type int
java.util.ArrayList<com.sosnoski.generics.DirInfo> fields:
  serialVersionUID of type long
  elementData of type com.sosnoski.generics.DirInfo[]
  size of type int
com.sosnoski.generics.DirInfo fields:
  m_files of type java.util.List<com.sosnoski.generics.FileInfo>
  m_directories of type java.util.List<com.sosnoski.generics.DirInfo>
  m_lastModify of type java.util.Date
java.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> 分析顯示出它有一個 String[] 字段。這個輸出也顯示了數據結構分析的局限 性:一旦遇到 DirInfo 類中的 List 字段,就沒法進一步分解了,因為 java.util.List 是接口而不是 實際的類 —— 而且只有接口的實現才有字段。當然,對本文提供的代碼做些修改,也可以把同樣的分析 像用於字段一樣用於方法,而且這種分析方法也能包含接口。

泛型結束語

到現在,已經看到了如何用 ASM 框架實現泛型數據結構分析,還有一個快速的示例,表現了這類分析 的可能性和局限性。還有一些比較好的應用,通過在工具中整合泛型,在 Java 類之間和一些外部形式之 間進行轉換,從而提供詳細的數據視圖。例如,我正計劃在我的 JiBX 數據綁定框架的新版本綁定生成器 中,利用泛型提供的信息,在不需要用戶的額外輸入的情況下,填充結構的更多細節。

在三篇關於泛型的文章之後,我要換一個主題了!接下來的類處理工具包 系列中,我將介紹一個處理 Java 5 標注的工具。以前,我曾經介紹過在使用標注作為配置信息的時候出現的問題。這個工具通過允 許在運行時覆蓋標注數據,試圖克服這些問題。覆蓋標注的能力,能不能讓它們用於更多類型的應用?請 關注下個月的文章來找到答案。

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved