/*
* 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.translate;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.devtools.j2objc.ast.AbstractTypeDeclaration;
import com.google.devtools.j2objc.ast.Annotation;
import com.google.devtools.j2objc.ast.AnnotationTypeDeclaration;
import com.google.devtools.j2objc.ast.AnnotationTypeMemberDeclaration;
import com.google.devtools.j2objc.ast.Block;
import com.google.devtools.j2objc.ast.BodyDeclaration;
import com.google.devtools.j2objc.ast.CompilationUnit;
import com.google.devtools.j2objc.ast.EnumConstantDeclaration;
import com.google.devtools.j2objc.ast.EnumDeclaration;
import com.google.devtools.j2objc.ast.Expression;
import com.google.devtools.j2objc.ast.FieldDeclaration;
import com.google.devtools.j2objc.ast.FunctionDeclaration;
import com.google.devtools.j2objc.ast.MethodDeclaration;
import com.google.devtools.j2objc.ast.NativeExpression;
import com.google.devtools.j2objc.ast.NativeStatement;
import com.google.devtools.j2objc.ast.ReturnStatement;
import com.google.devtools.j2objc.ast.SingleVariableDeclaration;
import com.google.devtools.j2objc.ast.Statement;
import com.google.devtools.j2objc.ast.TreeUtil;
import com.google.devtools.j2objc.ast.TypeDeclaration;
import com.google.devtools.j2objc.ast.UnitTreeVisitor;
import com.google.devtools.j2objc.ast.VariableDeclarationFragment;
import com.google.devtools.j2objc.types.GeneratedExecutableElement;
import com.google.devtools.j2objc.types.GeneratedTypeElement;
import com.google.devtools.j2objc.types.NativeType;
import com.google.devtools.j2objc.util.ElementUtil;
import com.google.devtools.j2objc.util.ErrorUtil;
import com.google.devtools.j2objc.util.TypeUtil;
import com.google.devtools.j2objc.util.UnicodeUtils;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.TypeMirror;
/**
* Adds the __metadata method to classes to support reflection.
*/
public class MetadataWriter extends UnitTreeVisitor {
// Metadata structure version. Increment it when any structure changes are made.
public static final int METADATA_VERSION = 7;
private static final NativeType CLASS_INFO_TYPE = new NativeType("const J2ObjcClassInfo *");
private final ArrayType annotationArray;
private final ArrayType annotationArray2D;
public MetadataWriter(CompilationUnit unit) {
super(unit);
TypeMirror annotationType =
GeneratedTypeElement.newEmulatedInterface("java.lang.annotation.Annotation").asType();
annotationArray = typeUtil.getArrayType(annotationType);
annotationArray2D = typeUtil.getArrayType(annotationArray);
}
@Override
public void endVisit(TypeDeclaration node) {
visitType(node);
}
@Override
public void endVisit(EnumDeclaration node) {
visitType(node);
}
@Override
public void endVisit(AnnotationTypeDeclaration node) {
visitType(node);
}
private void visitType(AbstractTypeDeclaration node) {
TypeElement type = node.getTypeElement();
if (!translationUtil.needsReflection(type)) {
return;
}
ExecutableElement metadataElement =
GeneratedExecutableElement.newMethodWithSelector("__metadata", CLASS_INFO_TYPE, type)
.addModifiers(Modifier.STATIC, Modifier.PRIVATE);
MethodDeclaration metadataDecl = new MethodDeclaration(metadataElement);
metadataDecl.setHasDeclaration(false);
Block body = new Block();
metadataDecl.setBody(body);
new MetadataGenerator(node, body.getStatements()).generateClassMetadata();
node.addBodyDeclaration(metadataDecl);
}
/**
* Generates the metadata contents for a single type.
*/
private class MetadataGenerator {
private final AbstractTypeDeclaration typeNode;
private final TypeElement type;
private final String className;
private final List<Statement> stmts;
// Use a LinkedHashMap so that we can de-dupe values that are added to the pointer table.
private final LinkedHashMap<String, Integer> pointers = new LinkedHashMap<>();
private int annotationFuncCount = 0;
private MetadataGenerator(AbstractTypeDeclaration typeNode, List<Statement> stmts) {
this.typeNode = typeNode;
type = typeNode.getTypeElement();
className = nameTable.getFullName(type);
this.stmts = stmts;
}
private void generateClassMetadata() {
String fullName = nameTable.getFullName(type);
int methodMetadataCount = generateMethodsMetadata();
int fieldMetadataCount = generateFieldsMetadata();
String annotationsFunc = createAnnotationsFunction(typeNode);
String metadata = UnicodeUtils.format(
"static const J2ObjcClassInfo _%s = { "
+ "%s, %s, %%s, %s, %s, %d, 0x%x, %d, %d, %s, %s, %s, %s, %s };",
fullName,
cStr(ElementUtil.isAnonymous(type) ? "" : ElementUtil.getName(type)),
cStr(Strings.emptyToNull(ElementUtil.getName(ElementUtil.getPackage(type)))),
methodMetadataCount > 0 ? "methods" : "NULL",
fieldMetadataCount > 0 ? "fields" : "NULL",
METADATA_VERSION,
getTypeModifiers(type),
methodMetadataCount,
fieldMetadataCount,
cStrIdx(getTypeName(ElementUtil.getDeclaringClass(type))),
cStrIdx(getTypeList(ElementUtil.asTypes(ElementUtil.getDeclaredTypes(type)))),
cStrIdx(getEnclosingMethodSelector()),
cStrIdx(signatureGenerator.createClassSignature(type)),
funcPtrIdx(annotationsFunc));
// Add the pointer table in a second format pass since it's value is dependent on all other
// values.
metadata = UnicodeUtils.format(metadata, getPtrTableEntry());
stmts.add(new NativeStatement(metadata));
stmts.add(new ReturnStatement(new NativeExpression("&_" + fullName, CLASS_INFO_TYPE)));
}
private String getPtrTableEntry() {
if (pointers.isEmpty()) {
return "NULL";
}
if (pointers.size() > Short.MAX_VALUE) {
// Note that values greater that 2^15 and less than 2^16 will not result in a compile
// error even though the index type is declared as signed.
// This limit is more restrictive than existing limits on number of methods and fields
// imposed by the JVM class file format, which allows up to 2^16 each of methods and
// fields. Our limit is a few times smaller than Java's since we use a signed index, share
// the table between methods and fields, and have multiple entries for each method or
// field that can index into the table. See JVMS-4.11.
ErrorUtil.error(typeNode, "Too many metadata entries causing overflow.");
}
stmts.add(new NativeStatement(
"static const void *ptrTable[] = { " + Joiner.on(", ").join(pointers.keySet()) + " };"));
return "ptrTable";
}
private int generateMethodsMetadata() {
List<String> methodMetadata = new ArrayList<>();
List<String> selectorMetadata = new ArrayList<>();
int methodCount = 0;
for (MethodDeclaration decl : TreeUtil.getMethodDeclarations(typeNode)) {
ExecutableElement element = decl.getExecutableElement();
// Skip synthetic methods and enum constructors.
if (ElementUtil.isSynthetic(element)
|| (ElementUtil.isEnum(type) && ElementUtil.isConstructor(element))) {
continue;
}
String annotationsFunc = createAnnotationsFunction(decl);
String paramAnnotationsFunc = createParamAnnotationsFunction(decl);
methodMetadata.add(getMethodMetadata(element, annotationsFunc, paramAnnotationsFunc));
String selector = nameTable.getMethodSelector(element);
String metadata = UnicodeUtils.format("methods[%d].selector = @selector(%s);",
methodCount, selector);
++methodCount;
selectorMetadata.add(metadata);
}
if (typeNode instanceof AnnotationTypeDeclaration) {
// Add property accessor and static default methods.
for (AnnotationTypeMemberDeclaration decl : TreeUtil.getAnnotationMembers(typeNode)) {
String name = ElementUtil.getName(decl.getExecutableElement());
String returnType = getTypeName(decl.getExecutableElement().getReturnType());
String metadata = UnicodeUtils.format(" { NULL, %s, 0x%x, -1, -1, -1, -1, -1, -1 },\n",
cStr(returnType),
java.lang.reflect.Modifier.PUBLIC | java.lang.reflect.Modifier.ABSTRACT);
methodMetadata.add(metadata);
metadata = UnicodeUtils.format("methods[%d].selector = @selector(%s);",
methodCount, name);
++methodCount;
selectorMetadata.add(metadata);
}
}
if (methodMetadata.size() > 0) {
StringBuilder sb = new StringBuilder("static J2ObjcMethodInfo methods[] = {\n");
for (String metadata : methodMetadata) {
sb.append(metadata);
}
sb.append(" };");
stmts.add(new NativeStatement(sb.toString()));
stmts.add(new NativeStatement("#pragma clang diagnostic push"));
stmts.add(new NativeStatement(
"#pragma clang diagnostic ignored \"-Wobjc-multiple-method-names\""));
for (String selector : selectorMetadata) {
stmts.add(new NativeStatement(selector));
}
stmts.add(new NativeStatement("#pragma clang diagnostic pop"));
}
return methodMetadata.size();
}
private String getMethodMetadata(
ExecutableElement method, String annotationsFunc, String paramAnnotationsFunc) {
String methodName = ElementUtil.getName(method);
String selector = nameTable.getMethodSelector(method);
boolean isConstructor = ElementUtil.isConstructor(method);
if (selector.equals(methodName) || isConstructor) {
methodName = null; // Reduce redundant data.
}
int modifiers = getMethodModifiers(method) & ElementUtil.ACC_FLAG_MASK;
String returnTypeStr = isConstructor ? null : getTypeName(method.getReturnType());
return UnicodeUtils.format(" { NULL, %s, 0x%x, %s, %s, %s, %s, %s, %s },\n",
cStr(returnTypeStr), modifiers, cStrIdx(methodName),
cStrIdx(getTypeList(ElementUtil.asTypes(method.getParameters()))),
cStrIdx(getTypeList(method.getThrownTypes())),
cStrIdx(signatureGenerator.createMethodTypeSignature(method)),
funcPtrIdx(annotationsFunc), funcPtrIdx(paramAnnotationsFunc));
}
private int generateFieldsMetadata() {
List<String> fieldMetadata = new ArrayList<>();
if (typeNode instanceof EnumDeclaration) {
for (EnumConstantDeclaration decl : ((EnumDeclaration) typeNode).getEnumConstants()) {
String annotationsFunc = createAnnotationsFunction(decl);
fieldMetadata.add(generateFieldMetadata(decl.getVariableElement(), annotationsFunc));
}
}
for (FieldDeclaration decl : TreeUtil.getFieldDeclarations(typeNode)) {
// Fields that share a declaration can share an annotations function.
String annotationsFunc = createAnnotationsFunction(decl);
for (VariableDeclarationFragment f : decl.getFragments()) {
String metadata = generateFieldMetadata(f.getVariableElement(), annotationsFunc);
if (metadata != null) {
fieldMetadata.add(metadata);
}
}
}
if (fieldMetadata.size() > 0) {
StringBuilder sb = new StringBuilder("static const J2ObjcFieldInfo fields[] = {\n");
for (String metadata : fieldMetadata) {
sb.append(metadata);
}
sb.append(" };");
stmts.add(new NativeStatement(sb.toString()));
}
return fieldMetadata.size();
}
private String generateFieldMetadata(VariableElement var, String annotationsFunc) {
int modifiers = getFieldModifiers(var);
boolean isStatic = ElementUtil.isStatic(var);
String javaName = ElementUtil.getName(var);
String objcName = nameTable.getVariableShortName(var);
if ((isStatic && objcName.equals(javaName))
|| (!isStatic && objcName.equals(javaName + '_'))) {
// Don't print Java name if it matches the default pattern, to conserve space.
javaName = null;
}
String staticRef = null;
String constantValue;
if (ElementUtil.isPrimitiveConstant(var)) {
constantValue = UnicodeUtils.format(".constantValue.%s = %s",
getRawValueField(var), nameTable.getVariableQualifiedName(var));
} else {
// Explicit 0-initializer to avoid Clang warning.
constantValue = ".constantValue.asLong = 0";
if (isStatic) {
staticRef = nameTable.getVariableQualifiedName(var);
}
}
return UnicodeUtils.format(
" { %s, %s, %s, 0x%x, %s, %s, %s, %s },\n",
cStr(objcName), cStr(getTypeName(var.asType())), constantValue, modifiers,
cStrIdx(javaName), addressOfIdx(staticRef),
cStrIdx(signatureGenerator.createFieldTypeSignature(var)), funcPtrIdx(annotationsFunc));
}
private String getEnclosingMethodSelector() {
Element enclosing = type.getEnclosingElement();
return ElementUtil.isExecutableElement(enclosing)
? nameTable.getMethodSelector((ExecutableElement) enclosing) : null;
}
private String cStrIdx(String str) {
return getPointerIdx(str != null ? "\"" + str + "\"" : null);
}
private String addressOfIdx(String name) {
return getPointerIdx(name != null ? "&" + name : null);
}
// Same as addressOfIdx, but adds a (void *) cast to satisfy c++ compilers.
private String funcPtrIdx(String name) {
return getPointerIdx(name != null ? "(void *)&" + name : null);
}
private String getPointerIdx(String ptr) {
if (ptr == null) {
return "-1";
}
Integer idx = pointers.get(ptr);
if (idx == null) {
idx = pointers.size();
pointers.put(ptr, idx);
}
return idx.toString();
}
/**
* Generate a function that returns the annotations for a BodyDeclarations node.
*/
private String createAnnotationsFunction(BodyDeclaration decl) {
List<Annotation> runtimeAnnotations =
TreeUtil.getRuntimeAnnotationsList(decl.getAnnotations());
if (runtimeAnnotations.isEmpty()) {
return null;
}
return addAnnotationsFunction(createAnnotations(runtimeAnnotations));
}
/**
* Generate a function that returns the 2-dimentional array of annotations for method
* parameters.
*/
private String createParamAnnotationsFunction(MethodDeclaration method) {
List<SingleVariableDeclaration> params = method.getParameters();
// Quick test to see if there are any parameter annotations.
boolean hasAnnotations = false;
for (SingleVariableDeclaration param : params) {
if (!Iterables.isEmpty(TreeUtil.getRuntimeAnnotations(param.getAnnotations()))) {
hasAnnotations = true;
break;
}
}
if (!hasAnnotations) {
return null;
}
List<Expression> subArrays = new ArrayList<>();
for (SingleVariableDeclaration param : params) {
subArrays.add(createAnnotations(
TreeUtil.getRuntimeAnnotationsList(param.getAnnotations())));
}
return addAnnotationsFunction(
translationUtil.createObjectArray(subArrays, annotationArray2D));
}
private String addAnnotationsFunction(Expression result) {
String name = className + "__Annotations$" + annotationFuncCount++;
FunctionDeclaration decl = new FunctionDeclaration(name, result.getTypeMirror());
decl.addModifiers(java.lang.reflect.Modifier.PRIVATE);
Block body = new Block();
decl.setBody(body);
body.addStatement(new ReturnStatement(result));
typeNode.addBodyDeclaration(decl);
return name;
}
}
private Expression createAnnotations(List<Annotation> annotations) {
List<Expression> expressions = new ArrayList<>();
for (Annotation annotation : annotations) {
expressions.add(translationUtil.createAnnotation(annotation.getAnnotationMirror()));
}
return translationUtil.createObjectArray(expressions, annotationArray);
}
private static String getRawValueField(VariableElement var) {
switch (var.asType().getKind()) {
case BOOLEAN: return "asBOOL";
case BYTE: return "asChar";
case CHAR: return "asUnichar";
case DOUBLE: return "asDouble";
case FLOAT: return "asFloat";
case INT: return "asInt";
case LONG: return "asLong";
case SHORT: return "asShort";
default: throw new AssertionError("Expected a primitive type.");
}
}
private String getTypeName(TypeMirror type) {
if (type == null) {
return null;
}
type = typeUtil.erasure(type);
if (TypeUtil.isDeclaredType(type)) {
return getTypeName(TypeUtil.asTypeElement(type));
} else if (TypeUtil.isArray(type)) {
return "[" + getTypeName(((ArrayType) type).getComponentType());
} else {
return TypeUtil.getBinaryName(type);
}
}
private String getTypeName(TypeElement type) {
return type == null ? null : "L" + nameTable.getFullName(type) + ";";
}
private String getTypeList(Iterable<? extends TypeMirror> types) {
if (Iterables.isEmpty(types)) {
return null;
}
StringBuilder sb = new StringBuilder();
for (TypeMirror type : types) {
sb.append(getTypeName(type));
}
return sb.toString();
}
/**
* Returns the modifiers for a specified type, including internal ones.
* All class modifiers are defined in the JVM specification, table 4.1.
*/
private static int getTypeModifiers(TypeElement type) {
int modifiers = ElementUtil.fromModifierSet(type.getModifiers());
if (type.getKind().isInterface()) {
modifiers |= java.lang.reflect.Modifier.INTERFACE | java.lang.reflect.Modifier.ABSTRACT
| java.lang.reflect.Modifier.STATIC;
}
if (ElementUtil.isSynthetic(type)) {
modifiers |= ElementUtil.ACC_SYNTHETIC;
}
if (ElementUtil.isAnnotationType(type)) {
modifiers |= ElementUtil.ACC_ANNOTATION;
}
if (ElementUtil.isEnum(type)) {
modifiers |= ElementUtil.ACC_ENUM;
}
if (ElementUtil.isAnonymous(type)) {
// Anonymous classes are always static, though their closure may include an instance.
modifiers |= ElementUtil.ACC_ANONYMOUS | java.lang.reflect.Modifier.STATIC;
}
return modifiers;
}
/**
* Returns the modifiers for a specified method, including internal ones.
* All method modifiers are defined in the JVM specification, table 4.5.
*/
private static int getMethodModifiers(ExecutableElement method) {
int modifiers = ElementUtil.fromModifierSet(method.getModifiers());
if (method.isVarArgs()) {
modifiers |= ElementUtil.ACC_VARARGS;
}
if (ElementUtil.isSynthetic(method)) {
modifiers |= ElementUtil.ACC_SYNTHETIC;
}
return modifiers;
}
/**
* Returns the modifiers for a specified field, including internal ones.
* All method modifiers are defined in the JVM specification, table 4.4.
*/
private static int getFieldModifiers(VariableElement var) {
int modifiers = ElementUtil.fromModifierSet(var.getModifiers());
if (ElementUtil.isSynthetic(var)) {
modifiers |= ElementUtil.ACC_SYNTHETIC;
}
if (ElementUtil.isEnumConstant(var)) {
modifiers |= ElementUtil.ACC_ENUM;
}
return modifiers;
}
private String cStr(String s) {
return s == null ? "NULL" : "\"" + s + "\"";
}
}