/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package signature.converter.dex;
import static signature.converter.dex.DexUtil.getClassName;
import static signature.converter.dex.DexUtil.getPackageName;
import signature.model.IClassDefinition;
import signature.model.IClassReference;
import signature.model.IConstructor;
import signature.model.IGenericDeclaration;
import signature.model.IMethod;
import signature.model.ITypeReference;
import signature.model.ITypeVariableDefinition;
import signature.model.ITypeVariableReference;
import signature.model.impl.SigArrayType;
import signature.model.impl.SigParameterizedType;
import signature.model.impl.SigPrimitiveType;
import signature.model.impl.SigTypeVariableDefinition;
import signature.model.impl.SigWildcardType;
import signature.model.impl.Uninitialized;
import signature.model.util.ITypeFactory;
import java.lang.reflect.GenericSignatureFormatError;
import java.util.ArrayList;
import java.util.List;
/**
* Implements a parser for the generics signature attribute. Uses a top-down,
* recursive descent parsing approach for the following grammar:
*
* <pre>
* ClassSignature ::=
* OptFormalTypeParams SuperclassSignature {SuperinterfaceSignature}.
* SuperclassSignature ::= ClassTypeSignature.
* SuperinterfaceSignature ::= ClassTypeSignature.
*
* OptFormalTypeParams ::=
* ["<" FormalTypeParameter {FormalTypeParameter} ">"].
*
* FormalTypeParameter ::= Ident ClassBound {InterfaceBound}.
* ClassBound ::= ":" [FieldTypeSignature].
* InterfaceBound ::= ":" FieldTypeSignature.
*
* FieldTypeSignature ::=
* ClassTypeSignature | ArrayTypeSignature | TypeVariableSignature.
* ArrayTypeSignature ::= "[" TypSignature.
*
* ClassTypeSignature ::=
* "L" {Ident "/"} Ident OptTypeArguments {"." Ident OptTypeArguments} ";".
*
* OptTypeArguments ::= "<" TypeArgument {TypeArgument} ">".
*
* TypeArgument ::= ([WildcardIndicator] FieldTypeSignature) | "*".
* WildcardIndicator ::= "+" | "-".
*
* TypeVariableSignature ::= "T" Ident ";".
*
* TypSignature ::= FieldTypeSignature | BaseType.
* BaseType ::= "B" | "C" | "D" | "F" | "I" | "J" | "S" | "Z".
*
* MethodTypeSignature ::=
* OptFormalTypeParams "(" {TypeSignature} ")" ReturnType {ThrowsSignature}.
* ThrowsSignature ::= ("^" ClassTypeSignature) | ("^" TypeVariableSignature).
*
* ReturnType ::= TypSignature | VoidDescriptor.
* VoidDescriptor ::= "V".
* </pre>
*/
public class GenericSignatureParser {
public List<ITypeReference> exceptionTypes;
public List<ITypeReference> parameterTypes;
public List<ITypeVariableDefinition> formalTypeParameters;
public ITypeReference returnType;
public ITypeReference fieldType;
public List<ITypeReference> interfaceTypes;
public ITypeReference superclassType;
private IGenericDeclaration genericDecl;
/*
* Parser:
*/
private char symbol; // 0: eof; else valid term symbol or first char of
// identifier.
private String identifier;
/*
* Scanner: eof is private to the scan methods and it's set only when a scan
* is issued at the end of the buffer.
*/
private boolean eof;
private char[] buffer;
private int pos;
private final ITypeFactory factory;
private final IClassInitializer classFinder;
private boolean parseForField;
public GenericSignatureParser(ITypeFactory factory,
IClassInitializer classFinder) {
this.factory = factory;
this.classFinder = classFinder;
}
private void setInput(IGenericDeclaration genericDecl, String input) {
if (input != null) {
this.genericDecl = genericDecl;
this.buffer = input.toCharArray();
this.eof = false;
scanSymbol();
} else {
this.eof = true;
}
}
public ITypeReference parseNonGenericType(String typeSignature) {
setInput(null, typeSignature);
ITypeReference type = parsePrimitiveType();
if (type == null) {
type = parseFieldTypeSignature();
}
return type;
}
public ITypeReference parseNonGenericReturnType(String typeSignature) {
setInput(null, typeSignature);
ITypeReference returnType = parsePrimitiveType();
if (returnType == null) {
returnType = parseReturnType();
}
return returnType;
}
private ITypeReference parsePrimitiveType() {
switch (symbol) {
case 'B':
scanSymbol();
return SigPrimitiveType.BYTE_TYPE;
case 'C':
scanSymbol();
return SigPrimitiveType.CHAR_TYPE;
case 'D':
scanSymbol();
return SigPrimitiveType.DOUBLE_TYPE;
case 'F':
scanSymbol();
return SigPrimitiveType.FLOAT_TYPE;
case 'I':
scanSymbol();
return SigPrimitiveType.INT_TYPE;
case 'J':
scanSymbol();
return SigPrimitiveType.LONG_TYPE;
case 'S':
scanSymbol();
return SigPrimitiveType.SHORT_TYPE;
case 'Z':
scanSymbol();
return SigPrimitiveType.BOOLEAN_TYPE;
default:
return null;
}
}
/**
* Parses the generic signature of a class and creates the data structure
* representing the signature.
*
* @param classToProcess
* the GenericDeclaration calling this method
* @param signature
* the generic signature of the class
*/
public void parseForClass(IClassDefinition classToProcess,
String signature) {
setInput(classToProcess, signature);
if (!eof) {
parseClassSignature();
} else {
throw new IllegalStateException("Generic signature is invalid!");
}
}
/**
* Parses the generic signature of a method and creates the data structure
* representing the signature.
*
* @param genericDecl
* the GenericDeclaration calling this method
* @param signature
* the generic signature of the class
*/
public void parseForMethod(IMethod genericDecl, String signature) {
setInput(genericDecl, signature);
if (!eof) {
parseMethodTypeSignature();
} else {
throw new IllegalStateException("Generic signature is invalid!");
}
}
/**
* Parses the generic signature of a constructor and creates the data
* structure representing the signature.
*
* @param genericDecl
* the GenericDeclaration calling this method
* @param signature
* the generic signature of the class
*/
public void parseForConstructor(IConstructor genericDecl,
String signature) {
setInput(genericDecl, signature);
if (!eof) {
parseMethodTypeSignature();
} else {
throw new IllegalStateException("Generic signature is invalid!");
}
}
/**
* Parses the generic signature of a field and creates the data structure
* representing the signature.
*
* @param genericDecl
* the GenericDeclaration calling this method
* @param signature
* the generic signature of the class
*/
public void parseForField(IClassDefinition genericDecl, String signature) {
parseForField = true;
setInput(genericDecl, signature);
try {
if (!eof) {
this.fieldType = parseFieldTypeSignature();
} else {
throw new IllegalStateException(
"Generic signature is invalid!");
}
} finally {
parseForField = false;
}
}
private void parseClassSignature() {
// ClassSignature ::=
// OptFormalTypeParameters SuperclassSignature
// {SuperinterfaceSignature}.
parseOptFormalTypeParameters();
// SuperclassSignature ::= ClassTypeSignature.
this.superclassType = parseClassTypeSignature();
interfaceTypes = new ArrayList<ITypeReference>(16);
while (symbol > 0) {
// SuperinterfaceSignature ::= ClassTypeSignature.
interfaceTypes.add(parseClassTypeSignature());
}
}
private void parseOptFormalTypeParameters() {
// OptFormalTypeParameters ::=
// ["<" FormalTypeParameter {FormalTypeParameter} ">"].
List<ITypeVariableDefinition> typeParameters =
new ArrayList<ITypeVariableDefinition>();
if (symbol == '<') {
scanSymbol();
typeParameters.add(parseFormalTypeParameter());
while ((symbol != '>') && (symbol > 0)) {
typeParameters.add(parseFormalTypeParameter());
}
expect('>');
}
formalTypeParameters = typeParameters;
}
private SigTypeVariableDefinition parseFormalTypeParameter() {
// FormalTypeParameter ::= Ident ClassBound {InterfaceBound}.
scanIdentifier();
String name = identifier.intern();
SigTypeVariableDefinition typeVariable = factory.getTypeVariable(name,
genericDecl);
List<ITypeReference> bounds = new ArrayList<ITypeReference>();
// ClassBound ::= ":" [FieldTypeSignature].
expect(':');
if (symbol == 'L' || symbol == '[' || symbol == 'T') {
bounds.add(parseFieldTypeSignature());
}
while (symbol == ':') {
// InterfaceBound ::= ":" FieldTypeSignature.
scanSymbol();
bounds.add(parseFieldTypeSignature());
}
typeVariable.setUpperBounds(bounds);
return typeVariable;
}
/**
* Returns the generic declaration for the type variable with the specified
* name.
*
* @param variableName
* the name of the type variable
* @param declaration
* the declaration to start searching
* @return the declaration which defines the specified type variable
*/
private IGenericDeclaration getDeclarationOfTypeVariable(
String variableName, IClassDefinition declaration) {
assert variableName != null;
assert declaration != null;
if (!Uninitialized.isInitialized(declaration.getTypeParameters())) {
declaration = classFinder.initializeClass(declaration
.getPackageName(), declaration.getName());
}
for (ITypeVariableDefinition typeVariable : declaration
.getTypeParameters()) {
if (variableName.equals(typeVariable.getName())) {
return declaration;
}
}
return getDeclarationOfTypeVariable(variableName, declaration
.getDeclaringClass());
}
private ITypeReference parseFieldTypeSignature() {
// FieldTypeSignature ::= ClassTypeSignature | ArrayTypeSignature
// | TypeVariableSignature.
switch (symbol) {
case 'L':
return parseClassTypeSignature();
case '[':
// ArrayTypeSignature ::= "[" TypSignature.
scanSymbol();
SigArrayType arrayType = factory.getArrayType(parseTypeSignature());
return arrayType;
case 'T':
return parseTypeVariableSignature();
default:
throw new GenericSignatureFormatError();
}
}
private ITypeReference parseClassTypeSignature() {
// ClassTypeSignature ::= "L" {Ident "/"} Ident
// OptTypeArguments {"." Ident OptTypeArguments} ";".
expect('L');
StringBuilder qualIdent = new StringBuilder("L");
scanIdentifier();
while (symbol == '/') {
scanSymbol();
qualIdent.append(identifier).append("/");
scanIdentifier();
}
qualIdent.append(this.identifier);
List<ITypeReference> typeArgs = parseOptTypeArguments();
ITypeReference parentType = null;
String packageName = getPackageName(qualIdent.toString() + ";");
String className = getClassName(qualIdent.toString() + ";");
if (typeArgs.isEmpty()) {
parentType = factory.getClassReference(packageName, className);
} else {
IClassReference rawType = factory.getClassReference(packageName,
className);
SigParameterizedType parameterizedType = factory
.getParameterizedType(null, rawType, typeArgs);
parentType = parameterizedType;
}
ITypeReference typeToReturn = parentType;
// if owner type is a parameterized type, the types are separated by '.'
while (symbol == '.') {
// Deal with Member Classes:
scanSymbol();
scanIdentifier();
qualIdent.append("$").append(identifier);
typeArgs = parseOptTypeArguments();
ITypeReference memberType = null;
packageName = getPackageName(qualIdent.toString() + ";");
className = getClassName(qualIdent.toString() + ";");
if (typeArgs.isEmpty()) {
memberType = factory.getClassReference(packageName, className);
} else {
IClassReference rawType = factory.getClassReference(
packageName, className);
SigParameterizedType parameterizedType = factory
.getParameterizedType(parentType, rawType, typeArgs);
memberType = parameterizedType;
}
typeToReturn = memberType;
}
expect(';');
return typeToReturn;
}
private List<ITypeReference> parseOptTypeArguments() {
// OptTypeArguments ::= "<" TypeArgument {TypeArgument} ">".
List<ITypeReference> typeArgs = new ArrayList<ITypeReference>(8);
if (symbol == '<') {
scanSymbol();
typeArgs.add(parseTypeArgument());
while ((symbol != '>') && (symbol > 0)) {
typeArgs.add(parseTypeArgument());
}
expect('>');
}
return typeArgs;
}
private ITypeReference parseTypeArgument() {
// TypeArgument ::= (["+" | "-"] FieldTypeSignature) | "*".
List<ITypeReference> extendsBound = new ArrayList<ITypeReference>(1);
ITypeReference superBound = null;
if (symbol == '*') {
scanSymbol();
extendsBound.add(factory.getClassReference("java.lang", "Object"));
SigWildcardType wildcardType = factory.getWildcardType(superBound,
extendsBound);
return wildcardType;
} else if (symbol == '+') {
scanSymbol();
extendsBound.add(parseFieldTypeSignature());
SigWildcardType wildcardType = factory.getWildcardType(superBound,
extendsBound);
return wildcardType;
} else if (symbol == '-') {
scanSymbol();
superBound = parseFieldTypeSignature();
extendsBound.add(factory.getClassReference("java.lang", "Object"));
SigWildcardType wildcardType = factory.getWildcardType(superBound,
extendsBound);
return wildcardType;
} else {
return parseFieldTypeSignature();
}
}
private ITypeVariableReference parseTypeVariableSignature() {
// TypeVariableSignature ::= "T" Ident ";".
expect('T');
scanIdentifier();
expect(';');
IGenericDeclaration declaration = genericDecl;
if (!factory.containsTypeVariableDefinition(identifier, declaration)) {
// since a field is not a generic declaration, i need to treat it
// differently.
// the generic declaration
if (parseForField) {
declaration = getDeclarationOfTypeVariable(identifier,
(IClassDefinition) genericDecl);
} else {
declaration = getDeclarationOfTypeVariable(identifier,
genericDecl.getDeclaringClass());
}
// just create type variable
factory.getTypeVariable(identifier, declaration);
}
return factory.getTypeVariableReference(identifier, declaration);
}
private ITypeReference parseTypeSignature() {
switch (symbol) {
case 'B':
scanSymbol();
return SigPrimitiveType.BYTE_TYPE;
case 'C':
scanSymbol();
return SigPrimitiveType.CHAR_TYPE;
case 'D':
scanSymbol();
return SigPrimitiveType.DOUBLE_TYPE;
case 'F':
scanSymbol();
return SigPrimitiveType.FLOAT_TYPE;
case 'I':
scanSymbol();
return SigPrimitiveType.INT_TYPE;
case 'J':
scanSymbol();
return SigPrimitiveType.LONG_TYPE;
case 'S':
scanSymbol();
return SigPrimitiveType.SHORT_TYPE;
case 'Z':
scanSymbol();
return SigPrimitiveType.BOOLEAN_TYPE;
default:
// Not an elementary type, but a FieldTypeSignature.
return parseFieldTypeSignature();
}
}
private void parseMethodTypeSignature() {
// MethodTypeSignature ::= [FormalTypeParameters]
// "(" {TypeSignature} ")" ReturnType {ThrowsSignature}.
parseOptFormalTypeParameters();
parameterTypes = new ArrayList<ITypeReference>(16);
expect('(');
while (symbol != ')' && (symbol > 0)) {
parameterTypes.add(parseTypeSignature());
}
expect(')');
returnType = parseReturnType();
exceptionTypes = new ArrayList<ITypeReference>(8);
while (symbol == '^') {
scanSymbol();
// ThrowsSignature ::= ("^" ClassTypeSignature) |
// ("^" TypeVariableSignature).
if (symbol == 'T') {
exceptionTypes.add(parseTypeVariableSignature());
} else {
exceptionTypes.add(parseClassTypeSignature());
}
}
}
private ITypeReference parseReturnType() {
// ReturnType ::= TypeSignature | "V".
if (symbol != 'V') {
return parseTypeSignature();
} else {
scanSymbol();
return SigPrimitiveType.VOID_TYPE;
}
}
//
// Scanner:
//
private void scanSymbol() {
if (!eof) {
if (pos < buffer.length) {
symbol = buffer[pos];
pos++;
} else {
symbol = 0;
eof = true;
}
} else {
throw new GenericSignatureFormatError();
}
}
private void expect(char c) {
if (symbol == c) {
scanSymbol();
} else {
throw new GenericSignatureFormatError();
}
}
private boolean isStopSymbol(char ch) {
switch (ch) {
case ':':
case '/':
case ';':
case '<':
case '.':
return true;
}
return false;
}
// PRE: symbol is the first char of the identifier.
// POST: symbol = the next symbol AFTER the identifier.
private void scanIdentifier() {
if (!eof) {
StringBuilder identBuf = new StringBuilder(32);
if (!isStopSymbol(symbol)) {
identBuf.append(symbol);
do {
char ch = buffer[pos];
if ((ch >= 'a') && (ch <= 'z') || (ch >= 'A')
&& (ch <= 'Z') || !isStopSymbol(ch)) {
identBuf.append(buffer[pos]);
pos++;
} else {
identifier = identBuf.toString();
scanSymbol();
return;
}
} while (pos != buffer.length);
identifier = identBuf.toString();
symbol = 0;
eof = true;
} else {
// Ident starts with incorrect char.
symbol = 0;
eof = true;
throw new GenericSignatureFormatError();
}
} else {
throw new GenericSignatureFormatError();
}
}
}