| /* ******************************************************************* |
| * Copyright (c) 2005-2008 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 |
| * |
| * ******************************************************************/ |
| package org.aspectj.util; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.aspectj.util.GenericSignature.ArrayTypeSignature; |
| import org.aspectj.util.GenericSignature.BaseTypeSignature; |
| import org.aspectj.util.GenericSignature.ClassTypeSignature; |
| import org.aspectj.util.GenericSignature.FieldTypeSignature; |
| import org.aspectj.util.GenericSignature.FormalTypeParameter; |
| import org.aspectj.util.GenericSignature.MethodTypeSignature; |
| import org.aspectj.util.GenericSignature.SimpleClassTypeSignature; |
| import org.aspectj.util.GenericSignature.TypeArgument; |
| import org.aspectj.util.GenericSignature.TypeSignature; |
| import org.aspectj.util.GenericSignature.TypeVariableSignature; |
| |
| /** |
| * Parses the generic signature attribute as defined in the JVM spec. |
| * |
| * @author Adrian Colyer |
| * @author Andy Clement |
| */ |
| public class GenericSignatureParser { |
| |
| private String inputString; |
| 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 GenericSignature.ClassSignature parseAsClassSignature(String sig) { |
| this.inputString = sig; |
| tokenStream = tokenize(sig); |
| tokenIndex = 0; |
| GenericSignature.ClassSignature classSig = new GenericSignature.ClassSignature(); |
| // FormalTypeParameters-opt |
| if (maybeEat("<")) { |
| List<FormalTypeParameter> formalTypeParametersList = new ArrayList<FormalTypeParameter>(); |
| do { |
| formalTypeParametersList.add(parseFormalTypeParameter()); |
| } while (!maybeEat(">")); |
| classSig.formalTypeParameters = new FormalTypeParameter[formalTypeParametersList.size()]; |
| formalTypeParametersList.toArray(classSig.formalTypeParameters); |
| } |
| classSig.superclassSignature = parseClassTypeSignature(); |
| List<ClassTypeSignature> superIntSigs = new ArrayList<ClassTypeSignature>(); |
| 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) { |
| this.inputString = sig; |
| tokenStream = tokenize(sig); |
| tokenIndex = 0; |
| FormalTypeParameter[] formals = new FormalTypeParameter[0]; |
| TypeSignature returnType = null; |
| // FormalTypeParameters-opt |
| if (maybeEat("<")) { |
| List<FormalTypeParameter> formalTypeParametersList = new ArrayList<FormalTypeParameter>(); |
| do { |
| formalTypeParametersList.add(parseFormalTypeParameter()); |
| } while (!maybeEat(">")); |
| formals = new FormalTypeParameter[formalTypeParametersList.size()]; |
| formalTypeParametersList.toArray(formals); |
| } |
| // Parameters |
| eat("("); |
| List<TypeSignature> paramList = new ArrayList<TypeSignature>(); |
| while (!maybeEat(")")) { |
| FieldTypeSignature fsig = parseFieldTypeSignature(true); |
| if (fsig != null) { |
| paramList.add(fsig); |
| } else { |
| paramList.add(new GenericSignature.BaseTypeSignature(eatIdentifier())); |
| } |
| } |
| TypeSignature[] params = new TypeSignature[paramList.size()]; |
| paramList.toArray(params); |
| // return type |
| returnType = parseFieldTypeSignature(true); |
| if (returnType == null) |
| returnType = new GenericSignature.BaseTypeSignature(eatIdentifier()); |
| // throws |
| List<FieldTypeSignature> throwsList = new ArrayList<FieldTypeSignature>(); |
| while (maybeEat("^")) { |
| FieldTypeSignature fsig = parseFieldTypeSignature(false); |
| throwsList.add(fsig); |
| } |
| FieldTypeSignature[] throwsSigs = new FieldTypeSignature[throwsList.size()]; |
| throwsList.toArray(throwsSigs); |
| return new GenericSignature.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) { |
| this.inputString = 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;", "Ljava/lang/Object"); |
| } |
| // Optional InterfaceBounds |
| List<FieldTypeSignature> optionalBounds = new ArrayList<FieldTypeSignature>(); |
| 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("Expecting [,L, or T, but found " + tokenStream[tokenIndex] + " while unpacking " |
| + inputString); |
| } |
| } |
| |
| private ArrayTypeSignature parseArrayTypeSignature() { |
| // opening [ already eaten |
| 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 (tokenStream[tokenIndex].equals(".")) { |
| // outer type completed |
| outerType = new SimpleClassTypeSignature(identifier); |
| nestedTypes = parseNestedTypesHelper(ret); |
| } 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); |
| nestedTypes = parseNestedTypesHelper(ret); |
| } else { |
| throw new IllegalStateException("Expecting .,<, or ;, but found " + tokenStream[tokenIndex] + " while unpacking " |
| + inputString); |
| } |
| } |
| ret.append(";"); |
| if (outerType == null) |
| outerType = new SimpleClassTypeSignature(ret.toString()); |
| return new ClassTypeSignature(ret.toString(), outerType, nestedTypes); |
| } |
| |
| /** |
| * Helper method to digest nested types, slightly more complex than necessary to cope with some android related |
| * incorrect classes (see bug 406167) |
| */ |
| private SimpleClassTypeSignature[] parseNestedTypesHelper(StringBuffer ret) { |
| boolean brokenSignature = false; |
| SimpleClassTypeSignature[] nestedTypes; |
| List<SimpleClassTypeSignature> nestedTypeList = new ArrayList<SimpleClassTypeSignature>(); |
| while (maybeEat(".")) { |
| ret.append("."); |
| SimpleClassTypeSignature sig = parseSimpleClassTypeSignature(); |
| if (tokenStream[tokenIndex].equals("/")) { |
| if (!brokenSignature) { |
| System.err.println("[See bug 406167] Bad class file signature encountered, nested types appear package qualified, ignoring those incorrect pieces. Signature: "+inputString); |
| } |
| brokenSignature = true; |
| // hit something like: Lcom/a/a/b/t<TK;TV;>.com/a/a/b/af.com/a/a/b/ag; |
| // and we are looking at the '/' after the com |
| tokenIndex++; // pointing at the next identifier |
| while (tokenStream[tokenIndex+1].equals("/")) { |
| tokenIndex+=2; // jump over an 'identifier' '/' pair |
| } |
| // now tokenIndex is the final bit of the name (which we'll treat as the inner type name) |
| sig = parseSimpleClassTypeSignature(); |
| } |
| ret.append(sig.toString()); |
| nestedTypeList.add(sig); |
| }; |
| nestedTypes = new SimpleClassTypeSignature[nestedTypeList.size()]; |
| nestedTypeList.toArray(nestedTypes); |
| return 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<TypeArgument> typeArgs = new ArrayList<TypeArgument>(); |
| 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.length <= tokenIndex) |
| return false; |
| 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] + " while unpacking " |
| + inputString); |
| } |
| 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<String> tokens = new ArrayList<String>(); |
| StringBuffer identifier = new StringBuffer(); |
| boolean inParens = false; |
| boolean inArray = false; |
| boolean couldSeePrimitive = false; |
| 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("/"); |
| couldSeePrimitive = false; |
| break; |
| case ';': |
| if (identifier.length() > 0) |
| tokens.add(identifier.toString()); |
| identifier = new StringBuffer(); |
| tokens.add(";"); |
| couldSeePrimitive = true; |
| inArray = false; |
| 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 '.': |
| if (identifier.length() > 0) |
| tokens.add(identifier.toString()); |
| identifier = new StringBuffer(); |
| couldSeePrimitive = false; |
| tokens.add("."); |
| break; |
| case '(': |
| tokens.add("("); |
| inParens = true; |
| couldSeePrimitive = true; |
| break; |
| case ')': |
| tokens.add(")"); |
| inParens = false; |
| break; |
| case '[': |
| tokens.add("["); |
| couldSeePrimitive = true; |
| inArray = true; |
| break; |
| case 'B': |
| case 'C': |
| case 'D': |
| case 'F': |
| case 'I': |
| case 'J': |
| case 'S': |
| case 'V': |
| case 'Z': |
| if ((inParens || inArray) && couldSeePrimitive && identifier.length() == 0) { |
| tokens.add(new String("" + chars[index])); |
| } else { |
| identifier.append(chars[index]); |
| } |
| inArray = false; |
| break; |
| case 'L': |
| couldSeePrimitive = false; |
| // deliberate fall-through |
| 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; |
| } |
| |
| } |