/*
* 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 com.google.devtools.j2objc.gen;
import com.google.devtools.j2objc.util.ElementUtil;
import com.google.devtools.j2objc.util.TypeUtil;
import com.google.devtools.j2objc.util.UnicodeUtils;
import java.util.List;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.type.WildcardType;
/**
* Generates signatures for classes, fields and methods, as defined by the JVM spec, 4.3.4,
* which states:
* <p>
* <i>Signatures are used to encode Java programming language type information that is
* not part of the Java Virtual Machine type system, such as generic type and method
* declarations and parameterized types.</i>
* <p>
* In class files, these strings define Signature attributes. Signature attributes can
* be dumped from class files using "javap -v fully-qualified-class-name".
*
* @author Tom Ball
*/
public class SignatureGenerator {
private static final String JAVA_OBJECT_SIGNATURE = "Ljava/lang/Object;";
private final ElementUtil elementUtil;
private final TypeUtil typeUtil;
public SignatureGenerator(TypeUtil typeUtil) {
elementUtil = typeUtil.elementUtil();
this.typeUtil = typeUtil;
}
/**
* Create a signature for a specified type.
*
* @return the signature of the type.
*/
public String createTypeSignature(TypeMirror type) {
StringBuilder sb = new StringBuilder();
genTypeSignature(type, sb);
return sb.toString();
}
/**
* Create a class signature string for a specified type.
*
* @return the signature if class is generic, else null.
*/
public String createClassSignature(TypeElement type) {
if (!hasGenericSignature(type)) {
return null;
}
StringBuilder sb = new StringBuilder();
genClassSignature(type, sb);
return sb.toString();
}
/**
* Create a field signature string for a specified variable.
*
* @return the signature if field type is a type variable, else null.
*/
public String createFieldTypeSignature(VariableElement variable) {
if (!hasGenericSignature(variable.asType())) {
return null;
}
StringBuilder sb = new StringBuilder();
genTypeSignature(variable.asType(), sb);
return sb.toString();
}
/**
* Create a method signature string for a specified method or constructor.
*
* @return the signature if method is generic or use type variables, else null.
*/
public String createMethodTypeSignature(ExecutableElement method) {
if (!hasGenericSignature(method)) {
return null;
}
StringBuilder sb = new StringBuilder();
genMethodTypeSignature(method, sb);
return sb.toString();
}
public String createJniFunctionSignature(ExecutableElement method) {
// Mangle function name as described in JNI specification.
// http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html#wp615
StringBuilder sb = new StringBuilder();
sb.append("Java_");
String methodName = ElementUtil.getName(method);
TypeElement declaringClass = ElementUtil.getDeclaringClass(method);
PackageElement pkg = ElementUtil.getPackage(declaringClass);
if (pkg != null && !pkg.isUnnamed()) {
String pkgName = pkg.getQualifiedName().toString();
for (String part : pkgName.split("\\.")) {
sb.append(part);
sb.append('_');
}
}
jniMangleClass(declaringClass, sb);
sb.append('_');
sb.append(jniMangle(methodName));
// Check whether the method is overloaded.
int nameCount = 0;
for (ExecutableElement m : ElementUtil.getExecutables(declaringClass)) {
if (methodName.equals(ElementUtil.getName(m)) && ElementUtil.isNative(m)) {
nameCount++;
}
}
if (nameCount >= 2) {
// Overloaded native methods, append JNI-mangled parameter types.
sb.append("__");
for (VariableElement param : method.getParameters()) {
String type = createTypeSignature(typeUtil.erasure(param.asType()));
sb.append(jniMangle(type));
}
}
return sb.toString();
}
private static void jniMangleClass(TypeElement clazz, StringBuilder sb) {
TypeElement declaringClass = ElementUtil.getDeclaringClass(clazz);
if (declaringClass != null) {
jniMangleClass(declaringClass, sb);
sb.append("_00024"); // $
}
sb.append(jniMangle(ElementUtil.getName(clazz)));
}
private static String jniMangle(String s) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case '.': sb.append('_'); break;
case '/': sb.append('_'); break;
case '_': sb.append("_1"); break;
case ';': sb.append("_2"); break;
case '[': sb.append("_3"); break;
case '$': sb.append("_00024"); break;
default: {
Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
if (!Character.UnicodeBlock.BASIC_LATIN.equals(block)) {
sb.append(UnicodeUtils.format("_%05x", (int) c));
} else {
sb.append(c);
}
break;
}
}
}
return sb.toString();
}
private static boolean hasGenericSignature(TypeElement type) {
return !type.getTypeParameters().isEmpty() || hasGenericSignature(type.getSuperclass())
|| hasGenericSignature(type.getInterfaces());
}
private static boolean hasGenericSignature(TypeMirror type) {
if (type == null) {
return false;
}
while (TypeUtil.isArray(type)) {
type = ((ArrayType) type).getComponentType();
}
switch (type.getKind()) {
case TYPEVAR: return true;
case DECLARED: return !((DeclaredType) type).getTypeArguments().isEmpty();
default: return false;
}
}
private static boolean hasGenericSignature(Iterable<? extends TypeMirror> typeList) {
for (TypeMirror type : typeList) {
if (hasGenericSignature(type)) {
return true;
}
}
return false;
}
private static boolean hasGenericSignature(ExecutableElement method) {
// Is this method generic?
return !method.getTypeParameters().isEmpty() || hasGenericSignature(method.getReturnType())
// Are any of its parameters?
|| hasGenericSignature(ElementUtil.asTypes(method.getParameters()))
// Are any of its thrown exceptions ?
|| hasGenericSignature(method.getThrownTypes());
}
// Method comments are from libcore.reflect.GenericSignatureParser.
/**
* ClassSignature ::=
* OptFormalTypeParameters SuperclassSignature {SuperinterfaceSignature}.
*/
private void genClassSignature(TypeElement type, StringBuilder sb) {
genOptFormalTypeParameters(type.getTypeParameters(), sb);
// JDT returns null for an interface's superclass, but signatures expect Object.
if (type.getKind().isInterface()) {
sb.append(JAVA_OBJECT_SIGNATURE);
} else {
genTypeSignature(type.getSuperclass(), sb);
}
for (TypeMirror intrface : type.getInterfaces()) {
genTypeSignature(intrface, sb);
}
}
/**
* FormalTypeParameters:
* < FormalTypeParameter+ >
*
* FormalTypeParameter:
* Identifier ClassBound InterfaceBound*
*/
private void genOptFormalTypeParameters(
List<? extends TypeParameterElement> typeParameters, StringBuilder sb) {
if (!typeParameters.isEmpty()) {
sb.append('<');
for (TypeParameterElement typeParam : typeParameters) {
genFormalTypeParameter(typeParam, sb);
}
sb.append('>');
}
}
/**
* FormalTypeParameter ::= Ident ClassBound {InterfaceBound}.
*/
private void genFormalTypeParameter(TypeParameterElement typeParam, StringBuilder sb) {
sb.append(ElementUtil.getName(typeParam));
List<? extends TypeMirror> bounds = typeParam.getBounds();
if (bounds.isEmpty()) {
sb.append(':').append(JAVA_OBJECT_SIGNATURE);
} else {
if (TypeUtil.isInterface(bounds.get(0))) {
sb.append(':');
}
for (TypeMirror bound : bounds) {
sb.append(':');
genTypeSignature(bound, sb);
}
}
}
// TODO(kstanger): Figure out if this can replace TypeUtil.getSignatureName().
private void genTypeSignature(TypeMirror type, StringBuilder sb) {
switch (type.getKind()) {
case BOOLEAN:
case BYTE:
case CHAR:
case DOUBLE:
case FLOAT:
case INT:
case LONG:
case SHORT:
case VOID:
sb.append(TypeUtil.getBinaryName(type));
break;
case ARRAY:
// ArrayTypeSignature ::= "[" TypSignature.
sb.append('[');
genTypeSignature(((ArrayType) type).getComponentType(), sb);
break;
case DECLARED:
// ClassTypeSignature ::= "L" {Ident "/"} Ident
// OptTypeArguments {"." Ident OptTypeArguments} ";".
sb.append('L');
sb.append(elementUtil.getBinaryName(TypeUtil.asTypeElement(type)).replace('.', '/'));
genOptTypeArguments(((DeclaredType) type).getTypeArguments(), sb);
sb.append(';');
break;
case TYPEVAR:
// TypeVariableSignature ::= "T" Ident ";".
sb.append('T');
sb.append(ElementUtil.getName(((TypeVariable) type).asElement()));
sb.append(';');
break;
case WILDCARD:
// TypeArgument ::= (["+" | "-"] FieldTypeSignature) | "*".
TypeMirror upperBound = ((WildcardType) type).getExtendsBound();
TypeMirror lowerBound = ((WildcardType) type).getSuperBound();
if (upperBound != null) {
sb.append('+');
genTypeSignature(upperBound, sb);
} else if (lowerBound != null) {
sb.append('-');
genTypeSignature(lowerBound, sb);
} else {
sb.append('*');
}
break;
default:
throw new AssertionError("Unexpected type kind: " + type.getKind());
}
}
/**
* OptTypeArguments ::= "<" TypeArgument {TypeArgument} ">".
*/
private void genOptTypeArguments(List<? extends TypeMirror> typeArguments, StringBuilder sb) {
if (!typeArguments.isEmpty()) {
sb.append('<');
for (TypeMirror typeParam : typeArguments) {
genTypeSignature(typeParam, sb);
}
sb.append('>');
}
}
/**
* MethodTypeSignature ::= [FormalTypeParameters]
* "(" {TypeSignature} ")" ReturnType {ThrowsSignature}.
*/
private void genMethodTypeSignature(ExecutableElement method, StringBuilder sb) {
genOptFormalTypeParameters(method.getTypeParameters(), sb);
sb.append('(');
for (VariableElement param : method.getParameters()) {
genTypeSignature(param.asType(), sb);
}
sb.append(')');
genTypeSignature(method.getReturnType(), sb);
List<? extends TypeMirror> thrownTypes = method.getThrownTypes();
if (hasGenericSignature(thrownTypes)) {
for (TypeMirror thrownType : thrownTypes) {
sb.append('^');
genTypeSignature(thrownType, sb);
}
}
}
}