/*
* 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.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.devtools.j2objc.Options;
import com.google.devtools.j2objc.ast.AbstractTypeDeclaration;
import com.google.devtools.j2objc.ast.BodyDeclaration;
import com.google.devtools.j2objc.ast.CompilationUnit;
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.NativeDeclaration;
import com.google.devtools.j2objc.ast.SingleVariableDeclaration;
import com.google.devtools.j2objc.ast.TreeNode;
import com.google.devtools.j2objc.ast.TreeUtil;
import com.google.devtools.j2objc.ast.VariableDeclarationFragment;
import com.google.devtools.j2objc.util.ElementUtil;
import com.google.devtools.j2objc.util.NameTable;
import com.google.devtools.j2objc.util.TranslationEnvironment;
import com.google.devtools.j2objc.util.TypeUtil;
import com.google.devtools.j2objc.util.UnicodeUtils;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.List;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
/**
* The base class for TypeDeclarationGenerator and TypeImplementationGenerator,
* providing common routines.
*
* @author Tom Ball, Keith Stanger
*/
public abstract class TypeGenerator extends AbstractSourceGenerator {
// Convenient fields for use by subclasses.
protected final AbstractTypeDeclaration typeNode;
protected final TypeElement typeElement;
protected final CompilationUnit compilationUnit;
protected final TranslationEnvironment env;
protected final TypeUtil typeUtil;
protected final NameTable nameTable;
protected final String typeName;
protected final Options options;
protected final boolean parametersNonnullByDefault;
private final List<BodyDeclaration> declarations;
protected TypeGenerator(SourceBuilder builder, AbstractTypeDeclaration node) {
super(builder);
typeNode = node;
typeElement = node.getTypeElement();
compilationUnit = TreeUtil.getCompilationUnit(node);
env = compilationUnit.getEnv();
typeUtil = env.typeUtil();
nameTable = env.nameTable();
typeName = nameTable.getFullName(typeElement);
declarations = filterDeclarations(node.getBodyDeclarations());
options = env.options();
parametersNonnullByDefault = options.nullability()
&& env.elementUtil().areParametersNonnullByDefault(node.getTypeElement(), options);
}
protected boolean shouldPrintDeclaration(BodyDeclaration decl) {
return true;
}
private List<BodyDeclaration> filterDeclarations(Iterable<BodyDeclaration> declarations) {
List<BodyDeclaration> filteredDecls = Lists.newArrayList();
for (BodyDeclaration decl : declarations) {
if (shouldPrintDeclaration(decl)) {
filteredDecls.add(decl);
}
}
return filteredDecls;
}
private static final Predicate<VariableDeclarationFragment> IS_STATIC_FIELD =
new Predicate<VariableDeclarationFragment>() {
@Override
public boolean apply(VariableDeclarationFragment frag) {
// isGlobalVar includes non-static but final primitives, which are treated
// like static fields in J2ObjC.
return ElementUtil.isGlobalVar(frag.getVariableElement());
}
};
private static final Predicate<VariableDeclarationFragment> IS_INSTANCE_FIELD =
new Predicate<VariableDeclarationFragment>() {
@Override
public boolean apply(VariableDeclarationFragment frag) {
return ElementUtil.isInstanceVar(frag.getVariableElement());
}
};
private static final Predicate<BodyDeclaration> IS_OUTER_DECL = new Predicate<BodyDeclaration>() {
@Override
public boolean apply(BodyDeclaration decl) {
switch (decl.getKind()) {
case FUNCTION_DECLARATION:
return true;
case NATIVE_DECLARATION:
return ((NativeDeclaration) decl).isOuter();
default:
return false;
}
}
};
private static final Predicate<BodyDeclaration> IS_INNER_DECL = new Predicate<BodyDeclaration>() {
@Override
public boolean apply(BodyDeclaration decl) {
switch (decl.getKind()) {
case METHOD_DECLARATION:
return true;
case NATIVE_DECLARATION:
return !((NativeDeclaration) decl).isOuter();
default:
return false;
}
}
};
// This predicate returns true if the declaration generates implementation
// code inside a @implementation declaration.
private static final Predicate<BodyDeclaration> HAS_INNER_IMPL =
new Predicate<BodyDeclaration>() {
@Override
public boolean apply(BodyDeclaration decl) {
return decl.getKind() == TreeNode.Kind.METHOD_DECLARATION
&& !Modifier.isAbstract(((MethodDeclaration) decl).getModifiers());
}
};
protected abstract void printFunctionDeclaration(FunctionDeclaration decl);
protected abstract void printMethodDeclaration(MethodDeclaration decl);
protected abstract void printNativeDeclaration(NativeDeclaration decl);
private void printDeclaration(BodyDeclaration declaration) {
switch (declaration.getKind()) {
case FUNCTION_DECLARATION:
printFunctionDeclaration((FunctionDeclaration) declaration);
return;
case METHOD_DECLARATION:
printMethodDeclaration((MethodDeclaration) declaration);
return;
case NATIVE_DECLARATION:
printNativeDeclaration((NativeDeclaration) declaration);
return;
default:
break;
}
}
protected void printDeclarations(Iterable<? extends BodyDeclaration> declarations) {
for (BodyDeclaration declaration : declarations) {
printDeclaration(declaration);
}
}
protected boolean isInterfaceType() {
return typeElement.getKind().isInterface();
}
protected Iterable<VariableDeclarationFragment> getInstanceFields() {
return getInstanceFields(declarations);
}
protected Iterable<VariableDeclarationFragment> getAllInstanceFields() {
return getInstanceFields(typeNode.getBodyDeclarations());
}
private Iterable<VariableDeclarationFragment> getInstanceFields(List<BodyDeclaration> decls) {
return Iterables.filter(
TreeUtil.asFragments(Iterables.filter(decls, FieldDeclaration.class)),
IS_INSTANCE_FIELD);
}
protected Iterable<VariableDeclarationFragment> getStaticFields() {
return Iterables.filter(
TreeUtil.asFragments(Iterables.filter(declarations, FieldDeclaration.class)),
IS_STATIC_FIELD);
}
protected Iterable<VariableDeclarationFragment> getAllFields() {
return TreeUtil.asFragments(
Iterables.filter(typeNode.getBodyDeclarations(), FieldDeclaration.class));
}
protected Iterable<BodyDeclaration> getInnerDeclarations() {
return Iterables.filter(declarations, IS_INNER_DECL);
}
protected Iterable<BodyDeclaration> getOuterDeclarations() {
return Iterables.filter(declarations, IS_OUTER_DECL);
}
protected void printInnerDeclarations() {
printDeclarations(getInnerDeclarations());
}
protected void printOuterDeclarations() {
printDeclarations(getOuterDeclarations());
}
private boolean hasStaticAccessorMethods() {
if (!options.staticAccessorMethods()) {
return false;
}
for (VariableDeclarationFragment fragment : TreeUtil.getAllFields(typeNode)) {
if (ElementUtil.isStatic(fragment.getVariableElement())
&& !((FieldDeclaration) fragment.getParent()).hasPrivateDeclaration()) {
return true;
}
}
return false;
}
private boolean hasStaticMethods() {
return !Iterables.isEmpty(
Iterables.filter(ElementUtil.getMethods(typeElement), ElementUtil::isStatic));
}
protected boolean needsPublicCompanionClass() {
if (typeNode.hasPrivateDeclaration()) {
return false;
}
return hasInitializeMethod()
|| hasStaticAccessorMethods()
|| ElementUtil.isRuntimeAnnotation(typeElement)
|| hasStaticMethods();
}
protected boolean needsCompanionClass() {
return needsPublicCompanionClass()
|| !Iterables.isEmpty(Iterables.filter(typeNode.getBodyDeclarations(), HAS_INNER_IMPL));
}
protected boolean hasInitializeMethod() {
return !typeNode.getClassInitStatements().isEmpty();
}
protected boolean needsTypeLiteral() {
return !(ElementUtil.isPackageInfo(typeElement) || ElementUtil.isAnonymous(typeElement)
|| ElementUtil.isLambda(typeElement));
}
protected String getDeclarationType(VariableElement var) {
TypeMirror type = var.asType();
if (ElementUtil.isVolatile(var)) {
return "volatile_" + NameTable.getPrimitiveObjCType(type);
} else {
return nameTable.getObjCType(type);
}
}
/**
* Create an Objective-C method signature string.
*/
protected String getMethodSignature(MethodDeclaration m) {
StringBuilder sb = new StringBuilder();
ExecutableElement element = m.getExecutableElement();
char prefix = Modifier.isStatic(m.getModifiers()) ? '+' : '-';
String returnType = nameTable.getObjCType(element.getReturnType());
String selector = nameTable.getMethodSelector(element);
if (m.isConstructor()) {
returnType = "instancetype";
} else if (selector.equals("hash")) {
// Explicitly test hashCode() because of NSObject's hash return value.
returnType = "NSUInteger";
}
sb.append(UnicodeUtils.format("%c (%s%s)", prefix, returnType, nullability(element)));
List<SingleVariableDeclaration> params = m.getParameters();
String[] selParts = selector.split(":");
if (params.isEmpty()) {
assert selParts.length == 1 && !selector.endsWith(":");
sb.append(selParts[0]);
} else {
assert params.size() == selParts.length;
int baseLength = sb.length() + selParts[0].length();
for (int i = 0; i < params.size(); i++) {
if (i != 0) {
sb.append('\n');
sb.append(pad(baseLength - selParts[i].length()));
}
VariableElement var = params.get(i).getVariableElement();
String typeName = nameTable.getObjCType(var.asType());
sb.append(UnicodeUtils.format("%s:(%s%s)%s", selParts[i], typeName, nullability(var),
nameTable.getVariableShortName(var)));
}
}
return sb.toString();
}
/**
* Returns an Objective-C nullability attribute string if there is a matching
* JSR305 annotation, or an empty string.
*/
private String nullability(Element element) {
if (options.nullability()) {
if (ElementUtil.hasNullableAnnotation(element)) {
return " __nullable";
}
if (ElementUtil.isNonnull(element, parametersNonnullByDefault)) {
return " __nonnull";
}
}
return "";
}
protected String getFunctionSignature(FunctionDeclaration function) {
StringBuilder sb = new StringBuilder();
String returnType = nameTable.getObjCType(function.getReturnType().getTypeMirror());
returnType += returnType.endsWith("*") ? "" : " ";
sb.append(returnType).append(function.getName()).append('(');
for (Iterator<SingleVariableDeclaration> iter = function.getParameters().iterator();
iter.hasNext(); ) {
VariableElement var = iter.next().getVariableElement();
String paramType = nameTable.getObjCType(var.asType());
paramType += (paramType.endsWith("*") ? "" : " ");
sb.append(paramType + nameTable.getVariableShortName(var));
if (iter.hasNext()) {
sb.append(", ");
}
}
sb.append(')');
return sb.toString();
}
protected String generateExpression(Expression expr) {
return StatementGenerator.generate(expr, getBuilder().getCurrentLine());
}
}