/* ******************************************************************* * Copyright (c) 2005 Contributors * All rights reserved. * This program and the accompanying materials are made available * under the terms of the Eclipse Public License v 2.0 * which accompanies this distribution and is available at * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt * * Contributors: * Andy Clement (IBM) initial implementation * ******************************************************************/ package org.aspectj.apache.bcel.classfile.tests; import java.util.ArrayList; import java.util.List; import org.aspectj.apache.bcel.Constants; import org.aspectj.apache.bcel.classfile.Attribute; import org.aspectj.apache.bcel.classfile.ClassFormatException; import org.aspectj.apache.bcel.classfile.JavaClass; import org.aspectj.apache.bcel.classfile.Method; import org.aspectj.apache.bcel.classfile.Signature; import org.aspectj.apache.bcel.classfile.Utility; /** * Generics introduces more complex signature possibilities, they are no longer just * made up of primitives or big 'L' types. The addition of 'anglies' due to * parameterization and the ability to specify wildcards (possibly bounded) * when talking about parameterized types means we need to be much more sophisticated. * * * Notes: * Signatures are used to encode Java programming language type informaiton * that is not part of the JVM type system, such as generic type and method * declarations and parameterized types. This kind of information is * needed to support reflection and debugging, and by the Java compiler. * * ============================================= * * ClassTypeSignature = LPackageSpecifier* SimpleClassTypeSignature ClassTypeSignatureSuffix*; * * PackageSpecifier = Identifier/PackageSpecifier* * SimpleClassTypeSignature= Identifier TypeArguments(opt) * ClassTypeSignatureSuffix= .SimpleClassTypeSignature * TypeVariableSignature = TIdentifier; * TypeArguments = * TypeArgument = WildcardIndiciator(opt) FieldTypeSignature * * * WildcardIndicator = + * - * ArrayTypeSignature = [TypeSignature * TypeSignature = [FieldTypeSignature * [BaseType * * * Examples: * Ljava/util/List; == java.util.List * Ljava/util/List; == java.util.List * Ljava/util/List; == java.util.List * Ljava/util/List<+Ljava/lang/Number;>; == java.util.List * Ljava/util/List<-Ljava/lang/Number;>; == java.util.List * Ljava/util/List<*>; == java.util.List * Ljava/util/Map<*-Ljava/lang/Number;>; == java.util.Map * * ============================================= * * ClassSignature = FormalTypeParameters(opt) SuperclassSignature SuperinterfaceSignatures* * * optional formal type parameters then a superclass signature then a superinterface signature * * FormalTypeParameters = * FormalTypeParameter = Identifier ClassBound InterfaceBound* * ClassBound = :FieldTypeSignature(opt) * InterfaceBound = :FieldTypeSignature * * If it exists, a set of formal type parameters are contained in anglies and consist of an identifier a classbound (assumed to be * object if not specified) and then an optional list of InterfaceBounds * * SuperclassSignature = ClassTypeSignature * SuperinterfaceSignature = ClassTypeSignature * FieldTypeSignature = ClassTypeSignature * ArrayTypeSignature * TypeVariableSignature * * * MethodTypeSignature = FormalTypeParameters(opt) ( TypeSignature* ) ReturnType ThrowsSignature* * ReturnType = TypeSignature * VoidDescriptor * ThrowsSignature = ^ClassTypeSignature * ^TypeVariableSignature * * Examples: * * ;> * * ClassBound not supplied, Object assumed. Interface bound is Comparable * * "T:Ljava/lang/Object;:Ljava/lang/Comparable<-TT;>;","T extends java.lang.Object & java.lang.Comparable" * */ public class GenericSignatureParsingTest extends BcelTestCase { /** * Throw some generic format signatures at the BCEL signature * parsing code and see what it does. */ public void testParsingGenericSignatures_ClassTypeSignature() { // trivial checkClassTypeSignature("Ljava/util/List;","java.util.List"); // basics checkClassTypeSignature("Ljava/util/List;","java.util.List"); checkClassTypeSignature("Ljava/util/List;","java.util.List"); // madness checkClassTypeSignature("Ljava/util/List<+Ljava/lang/Number;>;","java.util.List"); checkClassTypeSignature("Ljava/util/List<-Ljava/lang/Number;>;","java.util.List"); checkClassTypeSignature("Ljava/util/List<*>;", "java.util.List"); checkClassTypeSignature("Ljava/util/Map<*-Ljava/lang/Number;>;","java.util.Map"); // with type params checkClassTypeSignature("Ljava/util/Collection;","java.util.Collection"); // arrays checkClassTypeSignature("Ljava/util/List<[Ljava/lang/String;>;","java.util.List"); checkClassTypeSignature("[Ljava/util/List;","java.util.List[]"); } public void testMethodTypeToSignature() { checkMethodTypeToSignature("void",new String[]{"java.lang.String[]","boolean"},"([Ljava/lang/String;Z)V"); checkMethodTypeToSignature("void",new String[]{"java.util.List"},"(Ljava/util/List;)V"); } public void testMethodSignatureToArgumentTypes() { checkMethodSignatureArgumentTypes("([Ljava/lang/String;Z)V",new String[]{"java.lang.String[]","boolean"}); // checkMethodSignatureArgumentTypes("(Ljava/util/List;)V",new String[]{"java.util.List"}); } public void testMethodSignatureReturnType() { checkMethodSignatureReturnType("([Ljava/lang/String;)Z","boolean"); } public void testLoadingGenerics() throws ClassNotFoundException { JavaClass clazz = getClassFromJar("PossibleGenericsSigs"); // J5TODO asc fill this bit in... } // helper methods below // These routines call BCEL to determine if it can correctly translate from one form to the other. private void checkClassTypeSignature(String sig, String expected) { StringBuffer result = new StringBuffer(); int p = GenericSignatureParsingTest.readClassTypeSignatureFrom(sig,0,result,false); assertTrue("Only swallowed "+p+" chars of this sig "+sig+" (len="+sig.length()+")",p==sig.length()); assertTrue("Expected '"+expected+"' but got '"+result.toString()+"'",result.toString().equals(expected)); } private void checkMethodTypeToSignature(String ret,String[] args,String expected) { String res = GenericSignatureParsingTest.methodTypeToSignature(ret,args); if (!res.equals(expected)) { fail("Should match. Got: "+res+" Expected:"+expected); } } private void checkMethodSignatureReturnType(String sig,String expected) { String result = GenericSignatureParsingTest.methodSignatureReturnType(sig,false); if (!result.equals(expected)) { fail("Should match. Got: "+result+" Expected:"+expected); } } private void checkMethodSignatureArgumentTypes(String in,String[] expected) { String[] result = GenericSignatureParsingTest.methodSignatureArgumentTypes(in,false); if (result.length!=expected.length) { fail("Expected "+expected.length+" entries to be returned but only got "+result.length); } for (int i = 0; i < expected.length; i++) { String string = result[i]; if (!string.equals(expected[i])) fail("Argument: "+i+" should have been "+expected[i]+" but was "+string); } } public Signature getSignatureAttribute(JavaClass clazz,String name) { Method m = getMethod(clazz,name); Attribute[] as = m.getAttributes(); for (Attribute attribute : as) { if (attribute.getName().equals("Signature")) { return (Signature) attribute; } } return null; } /** * Takes a string and consumes a single complete signature from it, returning * how many chars it consumed. The chopit flag indicates whether to shorten * type references ( java/lang/String => String ) * * FIXME asc this should also create some kind of object you can query for information about whether its parameterized, what the bounds are, etc... */ public static final int readClassTypeSignatureFrom(String signature, int posn, StringBuffer result, boolean chopit) { int idx = posn; try { switch (signature.charAt(idx)) { case 'B' : result.append("byte"); return 1; case 'C' : result.append("char"); return 1; case 'D' : result.append("double"); return 1; case 'F' : result.append("float"); return 1; case 'I' : result.append("int"); return 1; case 'J' : result.append("long"); return 1; case 'S' : result.append("short"); return 1; case 'Z' : result.append("boolean");return 1; case 'V' : result.append("void"); return 1; //FIXME ASC Need a state machine to check we are parsing the right stuff here ! case 'T' : idx++; int nextSemiIdx = signature.indexOf(';',idx); result.append(signature.substring(idx,nextSemiIdx)); return nextSemiIdx+1-posn; case '+' : result.append("? extends "); return readClassTypeSignatureFrom(signature,idx+1,result,chopit)+1; case '-' : result.append("? super "); return readClassTypeSignatureFrom(signature,idx+1,result,chopit)+1; case '*' : result.append("?"); return 1; case 'L' : // Full class name boolean parameterized = false; int idxSemicolon = signature.indexOf(';',idx); // Look for closing ';' or '<' int idxAngly = signature.indexOf('<',idx); int endOfSig = idxSemicolon; if ((idxAngly!=-1) && idxAngly");idx++; } if (signature.charAt(idx)!=';') throw new RuntimeException("Did not find ';' at end of signature, found "+signature.charAt(idx)); idx++; return idx-posn; case '[' : // Array declaration int dim = 0; while (signature.charAt(idx)=='[') {dim++;idx++;} idx+=readClassTypeSignatureFrom(signature,idx,result,chopit); while (dim>0) {result.append("[]");dim--;} return idx-posn; default : throw new ClassFormatException("Invalid signature: `" + signature + "'"); } } catch(StringIndexOutOfBoundsException e) { // Should never occur throw new ClassFormatException("Invalid signature: " + e + ":" + signature); } } public static final String readClassTypeSignatureFrom(String signature) { StringBuffer sb = new StringBuffer(); GenericSignatureParsingTest.readClassTypeSignatureFrom(signature,0,sb,false); return sb.toString(); } public static int countBrackets(String brackets) { char[] chars = brackets.toCharArray(); int count = 0; boolean open = false; for (char aChar : chars) { switch (aChar) { case '[': if (open) throw new RuntimeException("Illegally nested brackets:" + brackets); open = true; break; case ']': if (!open) throw new RuntimeException("Illegally nested brackets:" + brackets); open = false; count++; break; default: } } if (open) throw new RuntimeException("Illegally nested brackets:" + brackets); return count; } /** * Parse Java type such as "char", or "java.lang.String[]" and return the * signature in byte code format, e.g. "C" or "[Ljava/lang/String;" respectively. * * @param type Java type * @return byte code signature */ public static String getSignature(String type) { StringBuilder buf = new StringBuilder(); char[] chars = type.toCharArray(); boolean char_found = false, delim = false; int index = -1; loop: for (int i=0; i < chars.length; i++) { switch (chars[i]) { case ' ': case '\t': case '\n': case '\r': case '\f': if (char_found) delim = true; break; case '[': if (!char_found) throw new RuntimeException("Illegal type: " + type); index = i; break loop; default: char_found = true; if (!delim) buf.append(chars[i]); } } int brackets = 0; if(index > 0) brackets = GenericSignatureParsingTest.countBrackets(type.substring(index)); type = buf.toString(); buf.setLength(0); for (int i=0; i < brackets; i++) buf.append('['); boolean found = false; for(int i=Constants.T_BOOLEAN; (i <= Constants.T_VOID) && !found; i++) { if (Constants.TYPE_NAMES[i].equals(type)) { found = true; buf.append(Constants.SHORT_TYPE_NAMES[i]); } } // Class name if (!found) buf.append('L' + type.replace('.', '/') + ';'); return buf.toString(); } /** * For some method signature (class file format) like '([Ljava/lang/String;)Z' this returns * the string representing the return type its 'normal' form, e.g. 'boolean' * * @param signature Method signature * @param chopit Shorten class names * @return return type of method */ public static final String methodSignatureReturnType(String signature,boolean chopit) throws ClassFormatException { int index; String type; try { // Read return type after `)' index = signature.lastIndexOf(')') + 1; type = Utility.signatureToString(signature.substring(index), chopit); } catch (StringIndexOutOfBoundsException e) { throw new ClassFormatException("Invalid method signature: " + signature); } return type; } /** * For some method signature (class file format) like '([Ljava/lang/String;)Z' this returns * the string representing the return type its 'normal' form, e.g. 'boolean' * * @param signature Method signature * @return return type of method * @throws ClassFormatException */ public static final String methodSignatureReturnType(String signature) throws ClassFormatException { return GenericSignatureParsingTest.methodSignatureReturnType(signature, true); } /** * For some method signature (class file format) like '([Ljava/lang/String;Z)V' this returns an array * of strings representing the arguments in their 'normal' form, e.g. '{java.lang.String[],boolean}' * * @param signature Method signature * @param chopit Shorten class names * @return Array of argument types */ public static final String[] methodSignatureArgumentTypes(String signature,boolean chopit) throws ClassFormatException { List vec = new ArrayList<>(); int index; String[] types; try { // Read all declarations between for `(' and `)' if (signature.charAt(0) != '(') throw new ClassFormatException("Invalid method signature: " + signature); index = 1; // current string position while(signature.charAt(index) != ')') { Utility.ResultHolder rh = Utility.signatureToStringInternal(signature.substring(index),chopit); vec.add(rh.getResult()); index += rh.getConsumedChars(); } } catch(StringIndexOutOfBoundsException e) { throw new ClassFormatException("Invalid method signature: " + signature); } types = new String[vec.size()]; vec.toArray(types); return types; } /** * Converts string containing the method return and argument types * to a byte code method signature. * * @param returnType Return type of method (e.g. "char" or "java.lang.String[]") * @param methodArgs Types of method arguments * @return Byte code representation of method signature */ public final static String methodTypeToSignature(String returnType, String[] methodArgs) throws ClassFormatException { StringBuilder buf = new StringBuilder("("); if (methodArgs != null) { for (String methodArg : methodArgs) { String str = GenericSignatureParsingTest.getSignature(methodArg); if (str.equals("V")) // void can't be a method argument throw new ClassFormatException("Invalid type: " + methodArg); buf.append(str); } } buf.append(")" + GenericSignatureParsingTest.getSignature(returnType)); return buf.toString(); } }