/*
* Copyright 2011 Google Inc. All Rights Reserved.
*
* 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.Block;
import com.google.devtools.j2objc.ast.ClassInstanceCreation;
import com.google.devtools.j2objc.ast.CompilationUnit;
import com.google.devtools.j2objc.ast.Expression;
import com.google.devtools.j2objc.ast.MethodDeclaration;
import com.google.devtools.j2objc.ast.MethodInvocation;
import com.google.devtools.j2objc.ast.ReturnStatement;
import com.google.devtools.j2objc.ast.SimpleName;
import com.google.devtools.j2objc.ast.SingleVariableDeclaration;
import com.google.devtools.j2objc.ast.StringLiteral;
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.types.ExecutablePair;
import com.google.devtools.j2objc.types.GeneratedExecutableElement;
import com.google.devtools.j2objc.types.GeneratedVariableElement;
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.Mappings;
import com.google.devtools.j2objc.util.NameTable;
import com.google.devtools.j2objc.util.TypeUtil;
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;
/**
* Translates invocations of mapped constructors to method invocation nodes.
* Adds copyWithZone methods to Cloneable types.
*
* @author Tom Ball
*/
public class JavaToIOSMethodTranslator extends UnitTreeVisitor {
private static final TypeMirror NSZONE_TYPE = new NativeType("NSZone *");
private static final ExecutableElement RETAIN_METHOD =
GeneratedExecutableElement.newMethodWithSelector(
NameTable.RETAIN_METHOD, TypeUtil.ID_TYPE, TypeUtil.NS_OBJECT)
.addModifiers(Modifier.PUBLIC);
public JavaToIOSMethodTranslator(CompilationUnit unit) {
super(unit);
}
@Override
public boolean visit(MethodDeclaration node) {
ExecutableElement method = node.getExecutableElement();
// Check if @ObjectiveCName is used but is mismatched with an overriden method.
String name = NameTable.getMethodNameFromAnnotation(method);
if (name != null) {
String selector = nameTable.selectorForMethodName(method, name);
String actualSelector = nameTable.getMethodSelector(method);
if (!selector.equals(actualSelector)) {
ErrorUtil.warning("ObjectiveCName(" + selector
+ "): Renamed method overrides a method with a different name.");
}
}
return true;
}
@Override
public boolean visit(ClassInstanceCreation node) {
// translate any embedded method invocations
if (node.getExpression() != null) {
node.getExpression().accept(this);
}
for (Expression e : node.getArguments()) {
e.accept(this);
}
if (node.getAnonymousClassDeclaration() != null) {
node.getAnonymousClassDeclaration().accept(this);
}
ExecutableElement method = node.getExecutableElement();
String key = Mappings.getMethodKey(method, typeUtil);
String selector = Mappings.STRING_CONSTRUCTOR_TO_METHOD_MAPPINGS.get(key);
if (selector != null) {
assert !node.hasRetainedResult();
if (key.equals("java.lang.String.<init>(Ljava/lang/String;)V")) {
// Special case: replace new String(constant) to constant (avoid clang warning).
Expression arg = node.getArgument(0);
if (arg instanceof StringLiteral) {
node.replaceWith(arg.copy());
return false;
}
}
ExecutableElement newElement = GeneratedExecutableElement.newMappedMethod(selector, method);
MethodInvocation newInvocation = new MethodInvocation(
new ExecutablePair(newElement), new SimpleName(ElementUtil.getDeclaringClass(method)));
TreeUtil.copyList(node.getArguments(), newInvocation.getArguments());
node.replaceWith(newInvocation);
}
return true;
}
@Override
public void endVisit(TypeDeclaration node) {
// If this type implements Cloneable but its parent doesn't, add a
// copyWithZone: method that calls clone().
TypeElement type = node.getTypeElement();
if (implementsCloneable(type.asType()) && !implementsCloneable(type.getSuperclass())) {
addCopyWithZoneMethod(node);
}
}
private boolean implementsCloneable(TypeMirror type) {
return type != null && typeUtil.findSupertype(type, "java.lang.Cloneable") != null;
}
private void addCopyWithZoneMethod(TypeDeclaration node) {
// Create copyWithZone: method.
GeneratedExecutableElement copyElement = GeneratedExecutableElement.newMethodWithSelector(
"copyWithZone:", TypeUtil.ID_TYPE, node.getTypeElement());
MethodDeclaration copyDecl = new MethodDeclaration(copyElement);
copyDecl.setHasDeclaration(false);
// Add NSZone *zone parameter.
VariableElement zoneParam =
GeneratedVariableElement.newParameter("zone", NSZONE_TYPE, copyElement);
copyElement.addParameter(zoneParam);
copyDecl.addParameter(new SingleVariableDeclaration(zoneParam));
Block block = new Block();
copyDecl.setBody(block);
ExecutableElement cloneElement = ElementUtil.findMethod(typeUtil.getJavaObject(), "clone");
MethodInvocation invocation = new MethodInvocation(new ExecutablePair(cloneElement), null);
if (options.useReferenceCounting()) {
invocation = new MethodInvocation(new ExecutablePair(RETAIN_METHOD), invocation);
}
block.addStatement(new ReturnStatement(invocation));
node.addBodyDeclaration(copyDecl);
}
}