/*
* 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.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.FieldDeclaration;
import com.google.devtools.j2objc.ast.FunctionDeclaration;
import com.google.devtools.j2objc.ast.MethodDeclaration;
import com.google.devtools.j2objc.ast.NativeDeclaration;
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.StringLiteral;
import com.google.devtools.j2objc.ast.TreeUtil;
import com.google.devtools.j2objc.ast.TypeLiteral;
import com.google.devtools.j2objc.ast.UnitTreeVisitor;
import com.google.devtools.j2objc.types.GeneratedExecutableElement;
import com.google.devtools.j2objc.types.GeneratedVariableElement;
import com.google.devtools.j2objc.util.ElementUtil;
import com.google.devtools.j2objc.util.NameTable;
import com.google.devtools.j2objc.util.TypeUtil;
import com.google.devtools.j2objc.util.UnicodeUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.AnnotationValue;
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.TypeMirror;
/**
* Adds fields and properties to annotation types.
* Generates reflection methods to provide the runtime annotations on types,
* methods and fields.
*/
public class AnnotationRewriter extends UnitTreeVisitor {
public AnnotationRewriter(CompilationUnit unit) {
super(unit);
}
@Override
public void endVisit(AnnotationTypeDeclaration node) {
TypeElement type = node.getTypeElement();
if (!ElementUtil.isRuntimeAnnotation(type)) {
return;
}
List<AnnotationTypeMemberDeclaration> members = TreeUtil.getAnnotationMembers(node);
List<BodyDeclaration> bodyDecls = node.getBodyDeclarations();
Map<ExecutableElement, VariableElement> fieldElements = createMemberFields(node, members);
addMemberProperties(node, members, fieldElements);
addDefaultAccessors(node, members);
bodyDecls.add(createAnnotationTypeMethod(type));
bodyDecls.add(createDescriptionMethod(type));
addConstructor(node, fieldElements);
}
// Create an instance field for each member.
private Map<ExecutableElement, VariableElement> createMemberFields(
AnnotationTypeDeclaration node, List<AnnotationTypeMemberDeclaration> members) {
TypeElement type = node.getTypeElement();
Map<ExecutableElement, VariableElement> fieldElements = new HashMap<>();
for (AnnotationTypeMemberDeclaration member : members) {
ExecutableElement memberElement = member.getExecutableElement();
String propName = NameTable.getAnnotationPropertyName(memberElement);
VariableElement field = GeneratedVariableElement.newField(
propName, memberElement.getReturnType(), type);
node.addBodyDeclaration(new FieldDeclaration(field, null));
fieldElements.put(memberElement, field);
}
return fieldElements;
}
// Generate the property declarations and synthesize statements.
private void addMemberProperties(
AnnotationTypeDeclaration node, List<AnnotationTypeMemberDeclaration> members,
Map<ExecutableElement, VariableElement> fieldElements) {
if (members.isEmpty()) {
return;
}
StringBuilder propertyDecls = new StringBuilder();
StringBuilder propertyImpls = new StringBuilder();
for (AnnotationTypeMemberDeclaration member : members) {
ExecutableElement memberElement = member.getExecutableElement();
String propName = NameTable.getAnnotationPropertyName(memberElement);
String memberTypeStr = nameTable.getObjCType(memberElement.getReturnType());
String fieldName = nameTable.getVariableShortName(fieldElements.get(memberElement));
propertyDecls.append(UnicodeUtils.format("@property (readonly) %s%s%s;\n",
memberTypeStr, memberTypeStr.endsWith("*") ? "" : " ", propName));
if (NameTable.needsObjcMethodFamilyNoneAttribute(propName)) {
propertyDecls.append(UnicodeUtils.format(
"- (%s)%s OBJC_METHOD_FAMILY_NONE;\n", memberTypeStr, propName));
}
propertyImpls.append(UnicodeUtils.format("@synthesize %s = %s;\n", propName, fieldName));
}
node.addBodyDeclaration(NativeDeclaration.newInnerDeclaration(
propertyDecls.toString(), propertyImpls.toString()));
}
// Create accessors for properties that have default values.
private void addDefaultAccessors(
AnnotationTypeDeclaration node, List<AnnotationTypeMemberDeclaration> members) {
TypeElement type = node.getTypeElement();
for (AnnotationTypeMemberDeclaration member : members) {
ExecutableElement memberElement = member.getExecutableElement();
AnnotationValue defaultValue = memberElement.getDefaultValue();
if (defaultValue == null || defaultValue.getValue() == null) {
continue;
}
TypeMirror memberType = memberElement.getReturnType();
String propName = NameTable.getAnnotationPropertyName(memberElement);
ExecutableElement defaultGetterElement = GeneratedExecutableElement.newMethodWithSelector(
propName + "Default", memberType, type)
.addModifiers(Modifier.STATIC);
MethodDeclaration defaultGetter = new MethodDeclaration(defaultGetterElement);
defaultGetter.setHasDeclaration(false);
Block defaultGetterBody = new Block();
defaultGetter.setBody(defaultGetterBody);
defaultGetterBody.addStatement(new ReturnStatement(
translationUtil.createAnnotationValue(memberType, defaultValue)));
node.addBodyDeclaration(defaultGetter);
}
}
private void addConstructor(
AnnotationTypeDeclaration node, Map<ExecutableElement, VariableElement> fieldElements) {
TypeElement type = node.getTypeElement();
String typeName = nameTable.getFullName(type);
FunctionDeclaration constructorDecl =
new FunctionDeclaration("create_" + typeName, type.asType());
Block constructorBody = new Block();
constructorDecl.setBody(constructorBody);
List<Statement> stmts = constructorBody.getStatements();
stmts.add(new NativeStatement(UnicodeUtils.format(
"%s *self = AUTORELEASE([[%s alloc] init]);", typeName, typeName)));
for (ExecutableElement memberElement : ElementUtil.getSortedAnnotationMembers(type)) {
TypeMirror memberType = memberElement.getReturnType();
String propName = NameTable.getAnnotationPropertyName(memberElement);
String fieldName = nameTable.getVariableShortName(fieldElements.get(memberElement));
VariableElement param = GeneratedVariableElement.newParameter(propName, memberType, null);
constructorDecl.addParameter(new SingleVariableDeclaration(param));
String rhs = TypeUtil.isReferenceType(memberType) ? "RETAIN_(" + propName + ")" : propName;
stmts.add(new NativeStatement("self->" + fieldName + " = " + rhs + ";"));
}
stmts.add(new NativeStatement("return self;"));
node.addBodyDeclaration(constructorDecl);
}
private MethodDeclaration createAnnotationTypeMethod(TypeElement type) {
ExecutableElement annotationTypeElement = GeneratedExecutableElement.newMethodWithSelector(
"annotationType", typeUtil.getJavaClass().asType(), type);
MethodDeclaration annotationTypeMethod = new MethodDeclaration(annotationTypeElement);
annotationTypeMethod.setHasDeclaration(false);
Block annotationTypeBody = new Block();
annotationTypeMethod.setBody(annotationTypeBody);
annotationTypeBody.addStatement(new ReturnStatement(new TypeLiteral(type.asType(), typeUtil)));
return annotationTypeMethod;
}
private MethodDeclaration createDescriptionMethod(TypeElement type) {
ExecutableElement descriptionElement = GeneratedExecutableElement.newMethodWithSelector(
"description", typeUtil.getJavaString().asType(), type);
MethodDeclaration descriptionMethod = new MethodDeclaration(descriptionElement);
descriptionMethod.setHasDeclaration(false);
Block descriptionBody = new Block();
descriptionMethod.setBody(descriptionBody);
descriptionBody.addStatement(new ReturnStatement(
new StringLiteral("@" + elementUtil.getBinaryName(type) + "()", typeUtil)));
return descriptionMethod;
}
}