/*
* 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.AbstractTypeDeclaration;
import com.google.devtools.j2objc.ast.AnnotationTypeDeclaration;
import com.google.devtools.j2objc.ast.Block;
import com.google.devtools.j2objc.ast.CompilationUnit;
import com.google.devtools.j2objc.ast.EnumDeclaration;
import com.google.devtools.j2objc.ast.MethodDeclaration;
import com.google.devtools.j2objc.ast.NativeStatement;
import com.google.devtools.j2objc.ast.SingleVariableDeclaration;
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.util.CodeReferenceMap;
import com.google.devtools.j2objc.util.ElementUtil;
import com.google.devtools.j2objc.util.TypeUtil;
import java.util.HashMap;
import java.util.Map;
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.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeMirror;
/**
* Checks for missing methods that would cause an ObjC compilation error. Adds stubs for existing
* abstract methods. Adds the ABSTRACT bit to a MethodDeclaration node if the method is a
* non-default one from an interface.
*
* @author Tom Ball, Keith Stanger
*/
public class AbstractMethodRewriter extends UnitTreeVisitor {
private final CodeReferenceMap deadCodeMap;
public AbstractMethodRewriter(CompilationUnit unit, CodeReferenceMap deadCodeMap) {
super(unit);
this.deadCodeMap = deadCodeMap;
}
@Override
public void endVisit(MethodDeclaration node) {
ExecutableElement methodElement = node.getExecutableElement();
if (!ElementUtil.isAbstract(methodElement)) {
return;
}
// JDT only adds the abstract bit to a MethodDeclaration node's modifiers if the abstract
// method is from a class. Since we want our code generator to go over an interface's
// method nodes for default method support and skip abstract methods, we add the bit if the
// method is from an interface.
TypeElement declaringClass = ElementUtil.getDeclaringClass(methodElement);
if (declaringClass.getKind().isInterface()) {
node.addModifiers(java.lang.reflect.Modifier.ABSTRACT);
return;
}
// There's no need to stub out an abstract method for an interface's companion class.
// Similarly, if this is an abstract method in a class and there's no need for reflection,
// we skip the stubbing out.
if (!translationUtil.needsReflection(declaringClass)) {
unit.setHasIncompleteProtocol();
unit.setHasIncompleteImplementation();
return;
}
Block body = new Block();
// Generate a body which throws a NSInvalidArgumentException.
String bodyCode = "// can't call an abstract method\n"
+ "[self doesNotRecognizeSelector:_cmd];";
if (!TypeUtil.isVoid(node.getReturnTypeMirror())) {
bodyCode += "\nreturn 0;"; // Never executes, but avoids a gcc warning.
}
body.addStatement(new NativeStatement(bodyCode));
node.setBody(body);
node.removeModifiers(java.lang.reflect.Modifier.ABSTRACT);
}
@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) {
addReturnTypeNarrowingDeclarations(node);
}
// Adds declarations for any methods where the known return type is more
// specific than what is already declared in inherited types.
private void addReturnTypeNarrowingDeclarations(AbstractTypeDeclaration node) {
TypeElement type = node.getTypeElement();
// No need to run this if the entire class is dead.
if (deadCodeMap != null && deadCodeMap.containsClass(type, elementUtil)) {
return;
}
Map<String, ExecutablePair> newDeclarations = new HashMap<>();
Map<String, TypeMirror> resolvedReturnTypes = new HashMap<>();
for (DeclaredType inheritedType : typeUtil.getObjcOrderedInheritedTypes(type.asType())) {
TypeElement inheritedElem = (TypeElement) inheritedType.asElement();
for (ExecutableElement methodElem : ElementUtil.getMethods(inheritedElem)) {
if (ElementUtil.isPrivate(methodElem)) {
continue;
}
TypeMirror declaredReturnType = typeUtil.erasure(methodElem.getReturnType());
if (!TypeUtil.isReferenceType(declaredReturnType)) {
continue; // Short circuit
}
String selector = nameTable.getMethodSelector(methodElem);
ExecutableType methodType = typeUtil.asMemberOf(inheritedType, methodElem);
TypeMirror returnType = typeUtil.erasure(methodType.getReturnType());
TypeMirror resolvedReturnType = resolvedReturnTypes.get(selector);
if (resolvedReturnType == null) {
resolvedReturnType = declaredReturnType;
resolvedReturnTypes.put(selector, resolvedReturnType);
} else if (!typeUtil.isSubtype(returnType, resolvedReturnType)) {
continue;
}
if (resolvedReturnType != returnType
&& !nameTable.getObjCType(resolvedReturnType).equals(
nameTable.getObjCType(returnType))) {
newDeclarations.put(selector, new ExecutablePair(methodElem, methodType));
resolvedReturnTypes.put(selector, returnType);
}
}
}
for (Map.Entry<String, ExecutablePair> newDecl : newDeclarations.entrySet()) {
if (deadCodeMap != null
&& deadCodeMap.containsMethod(newDecl.getValue().element(), typeUtil)) {
continue;
}
node.addBodyDeclaration(newReturnTypeNarrowingDeclaration(
newDecl.getKey(), newDecl.getValue(), type));
}
}
private MethodDeclaration newReturnTypeNarrowingDeclaration(
String selector, ExecutablePair method, TypeElement declaringClass) {
GeneratedExecutableElement element = GeneratedExecutableElement.newMethodWithSelector(
selector, method.type().getReturnType(), declaringClass)
// Preserve visibility of the original method.
.addModifiers(ElementUtil.getVisibilityModifiers(method.element()))
.addModifiers(Modifier.ABSTRACT);
MethodDeclaration decl = new MethodDeclaration(element);
if (!declaringClass.getKind().isInterface()) {
unit.setHasIncompleteImplementation();
}
int argCount = 0;
for (TypeMirror paramType : method.type().getParameterTypes()) {
VariableElement param = GeneratedVariableElement.newParameter(
"arg" + argCount++, paramType, element);
element.addParameter(param);
decl.addParameter(new SingleVariableDeclaration(param));
}
return decl;
}
}