應用類型替換深入挖掘使用泛型的程序的細節
簡介: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