From 85ca59ed0b8a0bfe4876d016fcbdf9b0e6d78416 Mon Sep 17 00:00:00 2001 From: acolyer Date: Fri, 1 Jul 2005 09:10:44 +0000 Subject: [PATCH] updates to Signature parsing for generics --- .../classfile/GenericSignatureParser.java | 366 +++++++++++++ .../apache/bcel/classfile/Signature.java | 483 ++++++------------ .../tests/GenericSignatureParserTest.java | 154 ++++++ 3 files changed, 674 insertions(+), 329 deletions(-) create mode 100644 bcel-builder/src/org/aspectj/apache/bcel/classfile/GenericSignatureParser.java create mode 100644 bcel-builder/testsrc/org/aspectj/apache/bcel/classfile/tests/GenericSignatureParserTest.java diff --git a/bcel-builder/src/org/aspectj/apache/bcel/classfile/GenericSignatureParser.java b/bcel-builder/src/org/aspectj/apache/bcel/classfile/GenericSignatureParser.java new file mode 100644 index 000000000..775a8b930 --- /dev/null +++ b/bcel-builder/src/org/aspectj/apache/bcel/classfile/GenericSignatureParser.java @@ -0,0 +1,366 @@ +/* ******************************************************************* + * Copyright (c) 2005 Contributors. + * All rights reserved. + * This program and the accompanying materials are made available + * under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution and is available at + * http://eclipse.org/legal/epl-v10.html + * + * Contributors: + * Adrian Colyer Initial implementation + * ******************************************************************/ +package org.aspectj.apache.bcel.classfile; + +import java.util.ArrayList; +import java.util.List; + +import org.aspectj.apache.bcel.classfile.Signature.ArrayTypeSignature; +import org.aspectj.apache.bcel.classfile.Signature.ClassTypeSignature; +import org.aspectj.apache.bcel.classfile.Signature.FieldTypeSignature; +import org.aspectj.apache.bcel.classfile.Signature.FormalTypeParameter; +import org.aspectj.apache.bcel.classfile.Signature.MethodTypeSignature; +import org.aspectj.apache.bcel.classfile.Signature.SimpleClassTypeSignature; +import org.aspectj.apache.bcel.classfile.Signature.TypeArgument; +import org.aspectj.apache.bcel.classfile.Signature.TypeSignature; +import org.aspectj.apache.bcel.classfile.Signature.TypeVariableSignature; +import org.aspectj.apache.bcel.classfile.Signature.BaseTypeSignature; + +/** + * Parses the generic signature attribute as defined in the JVM spec. + */ +public class GenericSignatureParser { + + private String[] tokenStream; // for parse in flight + private int tokenIndex = 0; + + /** + * AMC. + * Parse the signature string interpreting it as a ClassSignature according to + * the grammar defined in Section 4.4.4 of the JVM specification. + */ + public Signature.ClassSignature parseAsClassSignature(String sig) { + tokenStream = tokenize(sig); + tokenIndex = 0; + Signature.ClassSignature classSig = new Signature.ClassSignature(); + // FormalTypeParameters-opt + if (maybeEat("<")) { + List formalTypeParametersList = new ArrayList(); + do { + formalTypeParametersList.add(parseFormalTypeParameter()); + } while (!maybeEat(">")); + classSig.formalTypeParameters = new FormalTypeParameter[formalTypeParametersList.size()]; + formalTypeParametersList.toArray(classSig.formalTypeParameters); + } + classSig.superclassSignature = parseClassTypeSignature(); + List superIntSigs = new ArrayList(); + while (tokenIndex < tokenStream.length) { + superIntSigs.add(parseClassTypeSignature()); + } + classSig.superInterfaceSignatures = new ClassTypeSignature[superIntSigs.size()]; + superIntSigs.toArray(classSig.superInterfaceSignatures); + return classSig; + } + + /** + * AMC. + * Parse the signature string interpreting it as a MethodTypeSignature according to + * the grammar defined in Section 4.4.4 of the JVM specification. + */ + public MethodTypeSignature parseAsMethodSignature(String sig) { + tokenStream = tokenize(sig); + tokenIndex = 0; + FormalTypeParameter[] formals = new FormalTypeParameter[0]; + TypeSignature[] params = new TypeSignature[0]; + TypeSignature returnType = null; + FieldTypeSignature[] throwsSigs = new FieldTypeSignature[0]; + // FormalTypeParameters-opt + if (maybeEat("<")) { + List formalTypeParametersList = new ArrayList(); + do { + formalTypeParametersList.add(parseFormalTypeParameter()); + } while (!maybeEat(">")); + formals = new FormalTypeParameter[formalTypeParametersList.size()]; + formalTypeParametersList.toArray(formals); + } + // Parameters + eat("("); + List paramList = new ArrayList(); + while(!maybeEat(")")) { + FieldTypeSignature fsig = parseFieldTypeSignature(true); + if (fsig != null) { + paramList.add(fsig); + } else { + paramList.add(new Signature.BaseTypeSignature(eatIdentifier())); + } + } + params = new TypeSignature[paramList.size()]; + paramList.toArray(params); + // return type + returnType = parseFieldTypeSignature(true); + if (returnType == null) returnType = new Signature.BaseTypeSignature(eatIdentifier()); + // throws + List throwsList = new ArrayList(); + while (maybeEat("^")) { + FieldTypeSignature fsig = parseFieldTypeSignature(false); + throwsList.add(fsig); + } + throwsSigs = new FieldTypeSignature[throwsList.size()]; + throwsList.toArray(throwsSigs); + return new Signature.MethodTypeSignature(formals,params,returnType,throwsSigs); + } + + /** + * AMC. + * Parse the signature string interpreting it as a FieldTypeSignature according to + * the grammar defined in Section 4.4.4 of the JVM specification. + */ + public FieldTypeSignature parseAsFieldSignature(String sig) { + tokenStream = tokenize(sig); + tokenIndex = 0; + return parseFieldTypeSignature(false); + } + + private FormalTypeParameter parseFormalTypeParameter() { + FormalTypeParameter ftp = new FormalTypeParameter(); + // Identifier + ftp.identifier = eatIdentifier(); + // ClassBound + eat(":"); + ftp.classBound = parseFieldTypeSignature(true); + if (ftp.classBound == null) { + ftp.classBound = new ClassTypeSignature("Ljava/lang/Object;","Object"); + } + // Optional InterfaceBounds + List optionalBounds = new ArrayList(); + while (maybeEat(":")) { + optionalBounds.add(parseFieldTypeSignature(false)); + } + ftp.interfaceBounds = new FieldTypeSignature[optionalBounds.size()]; + optionalBounds.toArray(ftp.interfaceBounds); + return ftp; + } + + private FieldTypeSignature parseFieldTypeSignature(boolean isOptional) { + if (isOptional) { + // anything other than 'L', 'T' or '[' and we're out of here + if (!tokenStream[tokenIndex].startsWith("L") && + !tokenStream[tokenIndex].startsWith("T") && + !tokenStream[tokenIndex].startsWith("[")) { + return null; + } + } + if (maybeEat("[")) { + return parseArrayTypeSignature(); + } else if (tokenStream[tokenIndex].startsWith("L")) { + return parseClassTypeSignature(); + } else if (tokenStream[tokenIndex].startsWith("T")) { + return parseTypeVariableSignature(); + } else { + throw new IllegalStateException("Expection [,L, or T, but found " + + tokenStream[tokenIndex]); + } + } + + private ArrayTypeSignature parseArrayTypeSignature() { + // opening [ already eaten + eat("["); // grammar adds another one! + FieldTypeSignature fieldType = parseFieldTypeSignature(true); + if (fieldType != null) { + return new ArrayTypeSignature(fieldType); + } else { + // must be BaseType array + return new ArrayTypeSignature(new BaseTypeSignature(eatIdentifier())); + } + } + + // L PackageSpecifier* SimpleClassTypeSignature ClassTypeSignature* ; + private ClassTypeSignature parseClassTypeSignature() { + SimpleClassTypeSignature outerType = null; + SimpleClassTypeSignature[] nestedTypes = new SimpleClassTypeSignature[0]; + StringBuffer ret = new StringBuffer(); + String identifier = eatIdentifier(); + ret.append(identifier); + while (maybeEat("/")) { + ret.append("/"); // dont forget this... + ret.append(eatIdentifier()); + } + identifier = ret.toString(); + // now we have either a "." indicating the start of a nested type, + // or a "<" indication type arguments, or ";" and we are done. + while (!maybeEat(";")) { + if (maybeEat(".")) { + // outer type completed + outerType = new SimpleClassTypeSignature(identifier); + List nestedTypeList = new ArrayList(); + do { + ret.append("."); + SimpleClassTypeSignature sig = parseSimpleClassTypeSignature(); + ret.append(sig.toString()); + nestedTypeList.add(ret); + } while(maybeEat(".")); + nestedTypes = new SimpleClassTypeSignature[nestedTypeList.size()]; + nestedTypeList.toArray(nestedTypes); + } else if (tokenStream[tokenIndex].equals("<")) { + ret.append("<"); + TypeArgument[] tArgs = maybeParseTypeArguments(); + for (int i=0; i < tArgs.length; i++) { + ret.append(tArgs[i].toString()); + } + ret.append(">"); + outerType = new SimpleClassTypeSignature(identifier,tArgs); + // now parse possible nesteds... + List nestedTypeList = new ArrayList(); + while (maybeEat(".")) { + ret.append("."); + SimpleClassTypeSignature sig = parseSimpleClassTypeSignature(); + ret.append(sig.toString()); + nestedTypeList.add(ret); + } + nestedTypes = new SimpleClassTypeSignature[nestedTypeList.size()]; + nestedTypeList.toArray(nestedTypes); + } else { + throw new IllegalStateException("Expecting .,<, or ;, but found " + tokenStream[tokenIndex]); + } + } + ret.append(";"); + if (outerType == null) outerType = new SimpleClassTypeSignature(ret.toString()); + return new ClassTypeSignature(ret.toString(),outerType,nestedTypes); + } + + private SimpleClassTypeSignature parseSimpleClassTypeSignature() { + String identifier = eatIdentifier(); + TypeArgument[] tArgs = maybeParseTypeArguments(); + if (tArgs != null) { + return new SimpleClassTypeSignature(identifier,tArgs); + } else { + return new SimpleClassTypeSignature(identifier); + } + } + + private TypeArgument parseTypeArgument() { + boolean isPlus = false; + boolean isMinus = false; + if (maybeEat("*")) { + return new TypeArgument(); + } else if (maybeEat("+")) { + isPlus = true; + } else if (maybeEat("-")) { + isMinus = true; + } + FieldTypeSignature sig = parseFieldTypeSignature(false); + return new TypeArgument(isPlus,isMinus,sig); + } + + private TypeArgument[] maybeParseTypeArguments() { + if (maybeEat("<")) { + List typeArgs = new ArrayList(); + do { + TypeArgument arg = parseTypeArgument(); + typeArgs.add(arg); + } while(!maybeEat(">")); + TypeArgument[] tArgs = new TypeArgument[typeArgs.size()]; + typeArgs.toArray(tArgs); + return tArgs; + } else { + return null; + } + } + + private TypeVariableSignature parseTypeVariableSignature() { + TypeVariableSignature tv = new TypeVariableSignature(eatIdentifier()); + eat(";"); + return tv; + } + + private boolean maybeEat(String token) { + if (tokenStream[tokenIndex].equals(token)) { + tokenIndex++; + return true; + } + return false; + } + + private void eat(String token) { + if (!tokenStream[tokenIndex].equals(token)) { + throw new IllegalStateException("Expecting " + token + " but found " + tokenStream[tokenIndex]); + } + tokenIndex++; + } + + private String eatIdentifier() { + return tokenStream[tokenIndex++]; + } + + /** + * non-private for test visibility + * Splits a string containing a generic signature into tokens for consumption + * by the parser. + */ + public String[] tokenize(String signatureString) { + char[] chars = signatureString.toCharArray(); + int index = 0; + List tokens = new ArrayList(); + StringBuffer identifier = new StringBuffer(); + do { + switch (chars[index]) { + case '<' : + if (identifier.length() > 0) tokens.add(identifier.toString()); + identifier = new StringBuffer(); + tokens.add("<"); + break; + case '>' : + if (identifier.length() > 0) tokens.add(identifier.toString()); + identifier = new StringBuffer(); + tokens.add(">"); + break; + case ':' : + if (identifier.length() > 0) tokens.add(identifier.toString()); + identifier = new StringBuffer(); + tokens.add(":"); + break; + case '/' : + if (identifier.length() > 0) tokens.add(identifier.toString()); + identifier = new StringBuffer(); + tokens.add("/"); + break; + case ';' : + if (identifier.length() > 0) tokens.add(identifier.toString()); + identifier = new StringBuffer(); + tokens.add(";"); + break; + case '^': + tokens.add("^"); + break; + case '+': + tokens.add("+"); + break; + case '-': + tokens.add("-"); + break; + case '*': + tokens.add("*"); + break; + case '.' : + if (identifier.length() > 0) tokens.add(identifier.toString()); + identifier = new StringBuffer(); + tokens.add("."); + break; + case '(' : + tokens.add("("); + break; + case ')' : + tokens.add(")"); + case '[' : + tokens.add("["); + break; + default : + identifier.append(chars[index]); + } + } while((++index) < chars.length); + if (identifier.length() > 0) tokens.add(identifier.toString()); + String [] tokenArray = new String[tokens.size()]; + tokens.toArray(tokenArray); + return tokenArray; + } + +} diff --git a/bcel-builder/src/org/aspectj/apache/bcel/classfile/Signature.java b/bcel-builder/src/org/aspectj/apache/bcel/classfile/Signature.java index f05726596..c1546fac2 100644 --- a/bcel-builder/src/org/aspectj/apache/bcel/classfile/Signature.java +++ b/bcel-builder/src/org/aspectj/apache/bcel/classfile/Signature.java @@ -58,19 +58,20 @@ package org.aspectj.apache.bcel.classfile; */ -import org.aspectj.apache.bcel.Constants; - -import sun.reflect.generics.tree.SimpleClassTypeSignature; - -import java.io.*; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.List; +import org.aspectj.apache.bcel.Constants; + /** * This class is derived from Attribute and represents a reference * to a GJ attribute. * - * @version $Id: Signature.java,v 1.3 2005/06/26 20:29:23 acolyer Exp $ + * @version $Id: Signature.java,v 1.4 2005/07/01 09:10:45 acolyer Exp $ * @author M. Dahm * @see Attribute */ @@ -306,22 +307,136 @@ public final class Signature extends Attribute { // ============================================= // AMC extensions + private ClassSignature classSig; + private MethodTypeSignature methodSig; + private FieldTypeSignature fieldSig; + + public ClassSignature asClassSignature() { + if (classSig == null) { + GenericSignatureParser parser = new GenericSignatureParser(); + classSig = parser.parseAsClassSignature(getSignature()); + } + return classSig; + } + + public MethodTypeSignature asMethodTypeSignature() { + if (methodSig == null) { + GenericSignatureParser parser = new GenericSignatureParser(); + methodSig = parser.parseAsMethodSignature(getSignature()); + } + return methodSig; + } + + public FieldTypeSignature asFieldTypeSignature() { + if (fieldSig == null) { + GenericSignatureParser parser = new GenericSignatureParser(); + fieldSig = parser.parseAsFieldSignature(getSignature()); + } + return fieldSig; + } + + /** + * structure holding a parsed class signature + */ + public static class ClassSignature { + public FormalTypeParameter[] formalTypeParameters = new FormalTypeParameter[0]; + public ClassTypeSignature superclassSignature; + public ClassTypeSignature[] superInterfaceSignatures = new ClassTypeSignature[0]; + public String toString() { + StringBuffer ret = new StringBuffer(); + ret.append(formalTypeParameters.toString()); + ret.append(superclassSignature.toString()); + for (int i = 0; i < superInterfaceSignatures.length; i++) { + ret.append(superInterfaceSignatures[i].toString()); + } + return ret.toString(); + } + } + + public static class MethodTypeSignature { + public FormalTypeParameter[] formalTypeParameters = new FormalTypeParameter[0]; + public TypeSignature[] parameters = new TypeSignature[0]; + public TypeSignature returnType; + public FieldTypeSignature[] throwsSignatures = new FieldTypeSignature[0]; + + public MethodTypeSignature( + FormalTypeParameter[] aFormalParameterList, + TypeSignature[] aParameterList, + TypeSignature aReturnType, + FieldTypeSignature[] aThrowsSignatureList + ) { + this.formalTypeParameters = aFormalParameterList; + this.parameters = aParameterList; + this.returnType = aReturnType; + this.throwsSignatures = aThrowsSignatureList; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + if (formalTypeParameters.length > 0) { + sb.append("<"); + for (int i = 0; i < formalTypeParameters.length; i++) { + sb.append(formalTypeParameters[i].toString()); + } + sb.append(">"); + } + sb.append("("); + for (int i = 0; i < parameters.length; i++) { + sb.append(parameters[i].toString()); + } + sb.append(")"); + sb.append(returnType.toString()); + for (int i = 0; i < throwsSignatures.length; i++) { + sb.append("^"); + sb.append(throwsSignatures[i].toString()); + } + return sb.toString(); + } + } + /** * structure capturing a FormalTypeParameter from the Signature grammar */ - static class FormalTypeParameter { - String identifier; - FieldTypeSignature classBound; - FieldTypeSignature[] interfaceBounds; + public static class FormalTypeParameter { + public String identifier; + public FieldTypeSignature classBound; + public FieldTypeSignature[] interfaceBounds; + public String toString() { + StringBuffer ret = new StringBuffer(); + ret.append("T"); + ret.append(identifier); + ret.append(":"); + ret.append(classBound.toString()); + for (int i = 0; i < interfaceBounds.length; i++) { + ret.append(":"); + ret.append(interfaceBounds[i].toString()); + } + return ret.toString(); + } + } + + public static abstract class TypeSignature { + public boolean isBaseType() { return false; } + } + + public static class BaseTypeSignature extends TypeSignature { + private String sig; + public BaseTypeSignature(String aPrimitiveType) { + sig = aPrimitiveType; + } + public boolean isBaseType() { return true; } + public String toString() { + return sig; + } } - static abstract class FieldTypeSignature { - boolean isClassTypeSignature() { return false; } - boolean isTypeVariableSignature() { return false; } - boolean isArrayTypeSignature() { return false; } + public static abstract class FieldTypeSignature extends TypeSignature { + public boolean isClassTypeSignature() { return false; } + public boolean isTypeVariableSignature() { return false; } + public boolean isArrayTypeSignature() { return false; } } - static class ClassTypeSignature extends FieldTypeSignature { + public static class ClassTypeSignature extends FieldTypeSignature { public String classSignature; public SimpleClassTypeSignature outerType; public SimpleClassTypeSignature[] nestedTypes; @@ -335,7 +450,11 @@ public final class Signature extends Attribute { this.outerType = outer; this.nestedTypes = inners; } - boolean isClassTypeSignature() { return true; } + public boolean isClassTypeSignature() { return true; } + + public String toString() { + return classSignature; + } } static class TypeVariableSignature extends FieldTypeSignature { @@ -343,25 +462,24 @@ public final class Signature extends Attribute { public TypeVariableSignature(String typeVarToken) { this.typeVariableName = typeVarToken.substring(1); } - boolean isTypeVariableSignature() { return true; } + public boolean isTypeVariableSignature() { return true; } + public String toString() { + return "T" + typeVariableName + ";"; + } } static class ArrayTypeSignature extends FieldTypeSignature { - public FieldTypeSignature fieldTypeSig; - public String baseTypeSig; - public boolean isBaseTypeArray; - public ArrayTypeSignature(FieldTypeSignature aFieldSig) { - this.fieldTypeSig = aFieldSig; - isBaseTypeArray = false; + public TypeSignature typeSig; + public ArrayTypeSignature(TypeSignature aTypeSig) { + this.typeSig = aTypeSig; } - public ArrayTypeSignature(String aBaseType) { - this.baseTypeSig = aBaseType; - isBaseTypeArray = true; + public boolean isArrayTypeSignature() { return true; } + public String toString() { + return "[" + typeSig.toString(); } - boolean isArrayTypeSignature() { return true; } } - static class SimpleClassTypeSignature { + public static class SimpleClassTypeSignature { public String identifier; public TypeArgument[] typeArguments; @@ -389,7 +507,7 @@ public final class Signature extends Attribute { } } - static class TypeArgument { + public static class TypeArgument { public boolean isWildcard = false; public boolean isPlus = false; public boolean isMinus = false; @@ -405,307 +523,14 @@ public final class Signature extends Attribute { this.signature = aSig; } - } - - /** - * can't ask questions of signature content until it has been parsed. - */ - private boolean isParsed = false; - - private FormalTypeParameter[] formalTypeParameters; - private ClassTypeSignature superclassSignature; - private ClassTypeSignature[] superInterfaceSignatures; - - private String[] tokenStream; // for parse in flight - private int tokenIndex = 0; - - public FormalTypeParameter[] getFormalTypeParameters() { - if (!isParsed) throw new IllegalStateException("Must parse signature attribute first"); - return formalTypeParameters; - } - - public ClassTypeSignature getSuperclassSignature() { - if (!isParsed) throw new IllegalStateException("Must parse signature attribute first"); - return superclassSignature; - } - - public ClassTypeSignature[] getSuperInterfaceSignatures() { - if (!isParsed) throw new IllegalStateException("Must parse signature attribute first"); - return superInterfaceSignatures; - } - - /** - * AMC. - * Parse the signature string interpreting it as a ClassSignature according to - * the grammar defined in Section 4.4.4 of the JVM specification. - */ - public void parseAsClassSignature() { - tokenStream = tokenize(getSignature()); - tokenIndex = 0; - // FormalTypeParameters-opt - if (maybeEat("<")) { - List formalTypeParametersList = new ArrayList(); - do { - formalTypeParametersList.add(parseFormalTypeParameter()); - } while (!maybeEat(">")); - formalTypeParameters = new FormalTypeParameter[formalTypeParametersList.size()]; - formalTypeParametersList.toArray(formalTypeParameters); - } - superclassSignature = parseClassTypeSignature(); - List superIntSigs = new ArrayList(); - while (tokenIndex < tokenStream.length) { - superIntSigs.add(parseClassTypeSignature()); - } - superInterfaceSignatures = new ClassTypeSignature[superIntSigs.size()]; - superIntSigs.toArray(superInterfaceSignatures); - isParsed = true; - } - - /** - * AMC. - * Parse the signature string interpreting it as a MethodTypeSignature according to - * the grammar defined in Section 4.4.4 of the JVM specification. - */ - public void parseAsMethodSignature() { - isParsed = true; - } - - /** - * AMC. - * Parse the signature string interpreting it as a FieldTypeSignature according to - * the grammar defined in Section 4.4.4 of the JVM specification. - */ - public void parseAsFieldSignature() { - isParsed = true; - } - - private FormalTypeParameter parseFormalTypeParameter() { - FormalTypeParameter ftp = new FormalTypeParameter(); - // Identifier - ftp.identifier = eatIdentifier(); - // ClassBound - eat(":"); - ftp.classBound = parseFieldTypeSignature(true); - if (ftp.classBound == null) { - ftp.classBound = new ClassTypeSignature("Ljava/lang/Object;","Object"); - } - // Optional InterfaceBounds - List optionalBounds = new ArrayList(); - while (maybeEat(":")) { - optionalBounds.add(parseFieldTypeSignature(false)); - } - ftp.interfaceBounds = new FieldTypeSignature[optionalBounds.size()]; - optionalBounds.toArray(ftp.interfaceBounds); - return ftp; - } - - private FieldTypeSignature parseFieldTypeSignature(boolean isOptional) { - if (isOptional) { - // anything other than 'L', 'T' or '[' and we're out of here - if (!tokenStream[tokenIndex].startsWith("L") && - !tokenStream[tokenIndex].startsWith("T") && - !tokenStream[tokenIndex].startsWith("[")) { - return null; - } - } - if (maybeEat("[")) { - return parseArrayTypeSignature(); - } else if (tokenStream[tokenIndex].startsWith("L")) { - return parseClassTypeSignature(); - } else if (tokenStream[tokenIndex].startsWith("T")) { - return parseTypeVariableSignature(); - } else { - throw new IllegalStateException("Expection [,L, or T, but found " + - tokenStream[tokenIndex]); - } - } - - private ArrayTypeSignature parseArrayTypeSignature() { - // opening [ already eaten - eat("["); // grammar adds another one! - FieldTypeSignature fieldType = parseFieldTypeSignature(true); - if (fieldType != null) { - return new ArrayTypeSignature(fieldType); - } else { - // must be BaseType array - return new ArrayTypeSignature(eatIdentifier()); - } - } - - // L PackageSpecifier* SimpleClassTypeSignature ClassTypeSignature* ; - private ClassTypeSignature parseClassTypeSignature() { - SimpleClassTypeSignature outerType = null; - SimpleClassTypeSignature[] nestedTypes = new SimpleClassTypeSignature[0]; - StringBuffer ret = new StringBuffer(); - String identifier = eatIdentifier(); - ret.append(identifier); - while (maybeEat("/")) { - ret.append(eatIdentifier()); - } - // now we have either a "." indicating the start of a nested type, - // or a "<" indication type arguments, or ";" and we are done. - while (!maybeEat(";")) { - if (maybeEat(".")) { - // outer type completed - outerType = new SimpleClassTypeSignature(identifier); - List nestedTypeList = new ArrayList(); - do { - ret.append("."); - SimpleClassTypeSignature sig = parseSimpleClassTypeSignature(); - ret.append(sig.toString()); - nestedTypeList.add(ret); - } while(maybeEat(".")); - nestedTypes = new SimpleClassTypeSignature[nestedTypeList.size()]; - nestedTypeList.toArray(nestedTypes); - } else if (tokenStream[tokenIndex].equals("<")) { - ret.append("<"); - TypeArgument[] tArgs = maybeParseTypeArguments(); - for (int i=0; i < tArgs.length; i++) { - ret.append(tArgs[i].toString()); - } - ret.append(">"); - outerType = new SimpleClassTypeSignature(identifier,tArgs); - // now parse possible nesteds... - List nestedTypeList = new ArrayList(); - while (maybeEat(".")) { - ret.append("."); - SimpleClassTypeSignature sig = parseSimpleClassTypeSignature(); - ret.append(sig.toString()); - nestedTypeList.add(ret); - } - nestedTypes = new SimpleClassTypeSignature[nestedTypeList.size()]; - nestedTypeList.toArray(nestedTypes); - } else { - throw new IllegalStateException("Expecting .,<, or ;, but found " + tokenStream[tokenIndex]); - } - } - ret.append(";"); - return new ClassTypeSignature(ret.toString(),outerType,nestedTypes); - } - - private SimpleClassTypeSignature parseSimpleClassTypeSignature() { - String identifier = eatIdentifier(); - TypeArgument[] tArgs = maybeParseTypeArguments(); - if (tArgs != null) { - return new SimpleClassTypeSignature(identifier,tArgs); - } else { - return new SimpleClassTypeSignature(identifier); - } - } - - private TypeArgument parseTypeArgument() { - boolean isPlus = false; - boolean isMinus = false; - if (maybeEat("*")) { - return new TypeArgument(); - } else if (maybeEat("+")) { - isPlus = true; - } else if (maybeEat("-")) { - isMinus = true; - } - FieldTypeSignature sig = parseFieldTypeSignature(false); - return new TypeArgument(isPlus,isMinus,sig); - } - - private TypeArgument[] maybeParseTypeArguments() { - if (maybeEat("<")) { - List typeArgs = new ArrayList(); - do { - TypeArgument arg = parseTypeArgument(); - typeArgs.add(arg); - } while(!maybeEat(">")); - TypeArgument[] tArgs = new TypeArgument[typeArgs.size()]; - typeArgs.toArray(tArgs); - return tArgs; - } else { - return null; - } - } - - private TypeVariableSignature parseTypeVariableSignature() { - TypeVariableSignature tv = new TypeVariableSignature(eatIdentifier()); - eat(";"); - return tv; - } - - private boolean maybeEat(String token) { - if (tokenStream[tokenIndex].equals(token)) { - tokenIndex++; - return true; - } - return false; - } - - private void eat(String token) { - if (!tokenStream[tokenIndex].equals(token)) { - throw new IllegalStateException("Expecting " + token + " but found " + tokenStream[tokenIndex]); + public String toString() { + if (isWildcard) return "*"; + StringBuffer sb = new StringBuffer(); + if (isPlus) sb.append("+"); + if (isMinus) sb.append("-"); + sb.append(signature.toString()); + return sb.toString(); } - tokenIndex++; - } - - private String eatIdentifier() { - return tokenStream[tokenIndex++]; - } - - private String[] tokenize(String signatureString) { - char[] chars = signatureString.toCharArray(); - int index = 0; - List tokens = new ArrayList(); - StringBuffer identifier = new StringBuffer(); - do { - switch (chars[index]) { - case '<' : - tokens.add("<"); - break; - case '>' : - tokens.add(">"); - break; - case ':' : - if (identifier.length() > 0) tokens.add(identifier.toString()); - identifier = new StringBuffer(); - tokens.add(":"); - break; - case '/' : - if (identifier.length() > 0) tokens.add(identifier.toString()); - identifier = new StringBuffer(); - tokens.add("/"); - break; - case ';' : - if (identifier.length() > 0) tokens.add(identifier.toString()); - identifier = new StringBuffer(); - tokens.add(";"); - break; - case '^': - tokens.add("^"); - break; - case '+': - tokens.add("+"); - break; - case '-': - tokens.add("-"); - break; - case '*': - tokens.add("*"); - case '.' : - if (identifier.length() > 0) tokens.add(identifier.toString()); - identifier = new StringBuffer(); - tokens.add("."); - break; - case '(' : - tokens.add("("); - break; - case ')' : - tokens.add(")"); - case '[' : - break; - default : - identifier.append(chars[index]); - } - } while(index < chars.length); - if (identifier.length() > 0) tokens.add(identifier.toString()); - String [] tokenArray = new String[tokens.size()]; - tokens.toArray(tokenArray); - return tokenArray; } } diff --git a/bcel-builder/testsrc/org/aspectj/apache/bcel/classfile/tests/GenericSignatureParserTest.java b/bcel-builder/testsrc/org/aspectj/apache/bcel/classfile/tests/GenericSignatureParserTest.java new file mode 100644 index 000000000..e4d97a818 --- /dev/null +++ b/bcel-builder/testsrc/org/aspectj/apache/bcel/classfile/tests/GenericSignatureParserTest.java @@ -0,0 +1,154 @@ +package org.aspectj.apache.bcel.classfile.tests; + +import org.aspectj.apache.bcel.classfile.GenericSignatureParser; +import org.aspectj.apache.bcel.classfile.JavaClass; +import org.aspectj.apache.bcel.classfile.Signature.ClassSignature; +import org.aspectj.apache.bcel.classfile.Signature.ClassTypeSignature; +import org.aspectj.apache.bcel.classfile.Signature.FieldTypeSignature; +import org.aspectj.apache.bcel.classfile.Signature.SimpleClassTypeSignature; +import org.aspectj.apache.bcel.util.SyntheticRepository; + +import junit.framework.TestCase; + +public class GenericSignatureParserTest extends TestCase { + + GenericSignatureParser parser; + + public void testSimpleTokenize() { + String [] tokens = parser.tokenize("Ljava/lang/String;"); + assertEquals(new String[] {"Ljava","/","lang","/","String",";"},tokens); + } + + public void testTokenizeWithWildTypeArguments() { + String[] tokens = parser.tokenize("Ljava/lang/String<*>;"); + assertEquals(new String[] {"Ljava","/","lang","/","String","<","*",">",";"},tokens); + } + + public void testTokenizeWithExtendsTypeArguments() { + String[] tokens = parser.tokenize("Ljava/util/List<+TE>;"); + assertEquals(new String[] {"Ljava","/","util","/","List","<","+","TE",">",";"},tokens); + } + + public void testTokenizeWithSuperTypeArguments() { + String[] tokens = parser.tokenize("Ljava/util/List<-TE>;"); + assertEquals(new String[] {"Ljava","/","util","/","List","<","-","TE",">",";"},tokens); + } + + public void testTokenizeArrayType() { + String [] tokens = parser.tokenize("[Ljava/lang/String;"); + assertEquals(new String[] {"[","Ljava","/","lang","/","String",";"},tokens); + } + + public void testTokenizeFormalTypeParameters() { + String[] tokens = parser.tokenize(""); + assertEquals(new String[] {"<","T",":","Ljava","/","lang","/","String",";",":","Ljava","/","util","/","Comparable",";",">"},tokens); + } + + public void testParseClassSignatureSimple() { + ClassSignature sig = parser.parseAsClassSignature("Ljava/lang/String;"); + assertEquals("No type parameters",0,sig.formalTypeParameters.length); + assertEquals("No superinterfaces",0,sig.superInterfaceSignatures.length); + assertEquals("Ljava/lang/String;",sig.superclassSignature.classSignature); + SimpleClassTypeSignature outerType = sig.superclassSignature.outerType; + assertEquals("Ljava/lang/String;",outerType.identifier); + assertEquals("No type args",0,outerType.typeArguments.length); + } + + public void testParseClassSignatureTypeArgs() { + ClassSignature sig = parser.parseAsClassSignature("Ljava/util/List<+Ljava/lang/String;>;"); + assertEquals("No type parameters",0,sig.formalTypeParameters.length); + assertEquals("No superinterfaces",0,sig.superInterfaceSignatures.length); + assertEquals("Ljava/util/List<+Ljava/lang/String;>;",sig.superclassSignature.classSignature); + SimpleClassTypeSignature outerType = sig.superclassSignature.outerType; + assertEquals("Ljava/util/List",outerType.identifier); + assertEquals("One type arg",1,outerType.typeArguments.length); + assertTrue(outerType.typeArguments[0].isPlus); + assertEquals("+Ljava/lang/String;",outerType.typeArguments[0].toString()); + } + + public void testParseClassSignatureTheFullMonty() { + ClassSignature sig = parser.parseAsClassSignature(";>Ljava/util/List;Ljava/util/Comparable<-TE;>;"); + assertEquals("1 formal parameter",1,sig.formalTypeParameters.length); + assertEquals("E",sig.formalTypeParameters[0].identifier); + ClassTypeSignature fsig = (ClassTypeSignature) sig.formalTypeParameters[0].classBound; + assertEquals("Ljava/lang/String;",fsig.classSignature); + assertEquals("1 interface bound",1,sig.formalTypeParameters[0].interfaceBounds.length); + ClassTypeSignature isig = (ClassTypeSignature) sig.formalTypeParameters[0].interfaceBounds[0]; + assertEquals("Ljava/lang/Number;",isig.classSignature); + assertEquals("Ljava/util/List;",sig.superclassSignature.classSignature); + assertEquals("1 type argument",1,sig.superclassSignature.outerType.typeArguments.length); + assertEquals("TE;",sig.superclassSignature.outerType.typeArguments[0].toString()); + assertEquals("1 super interface",1,sig.superInterfaceSignatures.length); + assertEquals("Ljava/util/Comparable<-TE;>;",sig.superInterfaceSignatures[0].toString()); + } + + public void testClassSignatureParsingInJDK() throws Exception { + SyntheticRepository repository = SyntheticRepository.getInstance(); + String[] testClasses = new String[] { + "java.lang.Comparable", + "java.lang.Iterable", + "java.lang.Class", + "java.lang.Enum", + "java.lang.InheritableThreadLocal", + "java.lang.ThreadLocal", + "java.util.Collection", + "java.util.Comparator", + "java.util.Enumeration", + "java.util.Iterator", + "java.util.List", + "java.util.ListIterator", + "java.util.Map", + "java.util.Map$Entry", + "java.util.Queue", + "java.util.Set", + "java.util.SortedMap", + "java.util.SortedSet" + }; + for (int i = 0; i < testClasses.length; i++) { + JavaClass jc = repository.loadClass(testClasses[i]); + String sig = jc.getGenericSignature(); + parser.parseAsClassSignature(sig); + } + } + + public void testFieldSignatureParsingClassType() { + FieldTypeSignature fsig = parser.parseAsFieldSignature("Ljava/lang/String;"); + assertTrue("ClassTypeSignature", fsig instanceof ClassTypeSignature); + assertEquals("Ljava/lang/String;",fsig.toString()); + } + + private void assertEquals(String[] expected, String[] actual) { + if (actual.length != expected.length) { + int shorter = Math.min(expected.length,actual.length); + for (int i = 0; i < shorter; i++) { + if (!actual[i].equals(expected[i])) { + fail("Expected " + expected[i] + " at position " + i + " but found " + + actual[i]); + } + } + fail("Expected " + expected.length + " tokens but got " + actual.length + + tokensToString(actual)); + } + for (int i = 0; i < actual.length; i++) { + if (!actual[i].equals(expected[i])) { + fail("Expected " + expected[i] + " at position " + i + " but found " + + actual[i]); + } + } + } + + private String tokensToString(String[] tokens) { + StringBuffer sb = new StringBuffer(); + sb.append(tokens[0]); + for (int i = 1; i < tokens.length; i++) { + sb.append(","); + sb.append(tokens[i]); + } + return sb.toString(); + } + + protected void setUp() throws Exception { + super.setUp(); + parser = new GenericSignatureParser(); + } +} -- 2.39.5