blob: c6aae70819b63fc4634bd332e5f7b7a0c001fe33 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.android.jack.transformations.ast.inner;
import com.google.common.collect.Ordering;
import com.android.jack.Jack;
import com.android.jack.Options;
import com.android.jack.ir.SideEffectOperation;
import com.android.jack.ir.ast.JAlloc;
import com.android.jack.ir.ast.JAsgOperation;
import com.android.jack.ir.ast.JBinaryOperation;
import com.android.jack.ir.ast.JClassOrInterface;
import com.android.jack.ir.ast.JConstructor;
import com.android.jack.ir.ast.JDefinedClass;
import com.android.jack.ir.ast.JDefinedClassOrInterface;
import com.android.jack.ir.ast.JExpression;
import com.android.jack.ir.ast.JExpressionStatement;
import com.android.jack.ir.ast.JField;
import com.android.jack.ir.ast.JFieldRef;
import com.android.jack.ir.ast.JMethod;
import com.android.jack.ir.ast.JMethodCall;
import com.android.jack.ir.ast.JMethodCall.DispatchKind;
import com.android.jack.ir.ast.JMethodId;
import com.android.jack.ir.ast.JModifier;
import com.android.jack.ir.ast.JNewInstance;
import com.android.jack.ir.ast.JNode;
import com.android.jack.ir.ast.JNullLiteral;
import com.android.jack.ir.ast.JVisitor;
import com.android.jack.ir.ast.MethodKind;
import com.android.jack.ir.formatter.TypePackageAndMethodFormatter;
import com.android.jack.ir.impl.ResolutionTargetMarker;
import com.android.jack.ir.sourceinfo.SourceInfo;
import com.android.jack.transformations.ast.NewInstanceRemoved;
import com.android.jack.transformations.request.Replace;
import com.android.jack.transformations.request.TransformationRequest;
import com.android.jack.transformations.threeaddresscode.ThreeAddressCodeForm;
import com.android.jack.util.NamingTools;
import com.android.jack.util.filter.Filter;
import com.android.sched.item.Description;
import com.android.sched.item.Synchronized;
import com.android.sched.schedulable.Constraint;
import com.android.sched.schedulable.RunnableSchedulable;
import com.android.sched.schedulable.Transform;
import com.android.sched.util.config.ThreadConfig;
import java.util.Comparator;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
/**
* Generate accessors for outer fields and methods in an inner class
*/
@Description("Generate accessors for outer fields and methods in an inner class")
@Synchronized
@Transform(add = {GetterMarker.class,
SetterMarker.class,
WrapperMarker.class,
JMethodCall.class,
JNewInstance.class,
JNullLiteral.class,
JExpressionStatement.class,
InnerAccessorSchedulingSeparator.SeparatorTag.class},
remove = {ThreeAddressCodeForm.class, NewInstanceRemoved.class})
@Constraint(no = {SideEffectOperation.class, JAlloc.class})
public class InnerAccessorGenerator implements RunnableSchedulable<JDefinedClassOrInterface> {
@Nonnull
static final String THIS_PARAM_NAME = NamingTools.getNonSourceConflictingName("this");
private class Visitor extends JVisitor {
@Nonnull
protected final Filter<JMethod> filter = ThreadConfig.get(Options.METHOD_FILTER);
@CheckForNull
protected TransformationRequest tr;
@CheckForNull
private JDefinedClassOrInterface currentType = null;
/**
* Determines where the accessor must be located in case of super invocation
* @param declaringType where the member is declared
* @return the class where the accessor will be located
*/
@Nonnull
private JDefinedClassOrInterface getAccessorClassForSuperCall(
@Nonnull JDefinedClassOrInterface declaringType) {
// if the instance is the super of an enclosing class, we have to retrieve it
assert currentType != null;
JDefinedClass enclosing = (JDefinedClass) currentType;
while (!isSuperClassOf((JDefinedClass) declaringType, enclosing)) {
enclosing = (JDefinedClass) enclosing.getEnclosingType();
}
return enclosing;
}
/**
* Determines where the accessor must be located
* @param modifier the modifier of the member we try to access to
* @param declaringType where the member is declared
* @return the class where the accessor will be located
*/
@Nonnull
private JDefinedClassOrInterface getAccessorClass(int modifier,
@Nonnull JDefinedClassOrInterface declaringType) {
// Search the first class in which the member is accessible
// from the referencing type to its enclosing types
JDefinedClassOrInterface refType = currentType;
while (refType != null) {
if (isDirectlyVisibleFrom(modifier, declaringType, refType)) {
return refType;
}
refType = (JDefinedClassOrInterface) refType.getEnclosingType();
}
// If not found, the accessor must be in the declaring class
assert JModifier.isPrivate(modifier);
return declaringType;
}
/**
* Indicates that a field or method can be accessed without accessor
* @param modifier the modifier of the field or method
* @param declaringType the class where the field or method is declared
* @param type the type from where we want to know if the field or method is accessible
* @return true if the field or method is visible without accessor
*/
private boolean isDirectlyVisibleFrom(int modifier,
@Nonnull JDefinedClassOrInterface declaringType, @Nonnull JDefinedClassOrInterface type) {
if (JModifier.isPublic(modifier) || declaringType.isSameType(type)) {
return true;
}
if (JModifier.isPrivate(modifier)) {
// The case when type is the declaring type has already been treated
return false;
}
if (JModifier.isProtected(modifier) && type instanceof JDefinedClass
&& isSuperClassOf((JDefinedClass) declaringType, (JDefinedClass) type)) {
return true;
}
// The field is protected (but not from a super class) or package
// We test if both classes are in same package
return declaringType.getEnclosingPackage() == type.getEnclosingPackage();
}
@Override
public boolean visit(@Nonnull JFieldRef x) {
JNode parent = x.getParent();
JField field = x.getFieldId().getField();
assert field != null;
JDefinedClassOrInterface accessorClass = getAccessorClass(field.getModifier(),
field.getEnclosingType());
assert currentType != null;
assert tr != null;
if (!accessorClass.isSameType(currentType)) {
assert accessorClass.getSourceInfo().getFileSourceInfo()
.equals(currentType.getSourceInfo().getFileSourceInfo());
if (parent instanceof JAsgOperation
&& ((JAsgOperation) parent).getLhs() == x) {
// writing access
//
SetterMarker marker = accessorClass.getMarker(SetterMarker.class);
if (marker == null) {
marker = new SetterMarker();
accessorClass.addMarker(marker);
}
JMethod setter = marker.getOrCreateSetter(field, (JDefinedClass) accessorClass);
// this.this$0.field = $value => $set<id>(this.this$0, $value)
JBinaryOperation binOp = (JBinaryOperation) x.getParent();
JMethodId setterId = setter.getMethodId();
JMethodCall setterCall =
new JMethodCall(binOp.getSourceInfo(), null, accessorClass, setterId,
setter.getType(), setterId.canBeVirtual());
if (!field.isStatic()) {
JExpression instance = x.getInstance();
assert instance != null;
setterCall.addArg(instance);
}
setterCall.addArg(binOp.getRhs());
assert setterCall.getArgs().size() == setter.getParams().size();
tr.append(new Replace(binOp, setterCall));
} else {
// reading access
//
GetterMarker marker = accessorClass.getMarker(GetterMarker.class);
if (marker == null) {
marker = new GetterMarker();
accessorClass.addMarker(marker);
}
JMethod getter = marker.getOrCreateGetter(field, (JDefinedClass) accessorClass);
// this.this$0.field => $get<id>(this.this$0)
JMethodId getterId = getter.getMethodId();
JMethodCall getterCall = new JMethodCall(x.getSourceInfo(), null, accessorClass,
getterId, getter.getType(), getterId.canBeVirtual());
if (!field.isStatic()) {
JExpression instance = x.getInstance();
assert instance != null;
getterCall.addArg(instance);
}
assert getterCall.getArgs().size() == getter.getParams().size();
tr.append(new Replace(x, getterCall));
}
}
return super.visit(x);
}
@Override
public boolean visit(@Nonnull JMethodCall x) {
ResolutionTargetMarker resolutionTargetMarker = x.getMarker(ResolutionTargetMarker.class);
if (resolutionTargetMarker != null) {
JMethod method = resolutionTargetMarker.getTarget();
JDefinedClassOrInterface accessorClass;
boolean isSuper = x.getDispatchKind() == DispatchKind.DIRECT
&& method.getMethodId().getKind() == MethodKind.INSTANCE_VIRTUAL;
if (isSuper) {
accessorClass = getAccessorClassForSuperCall(method.getEnclosingType());
} else {
accessorClass = getAccessorClass(method.getModifier(),
method.getEnclosingType());
}
assert currentType != null;
assert tr != null;
if (!accessorClass.isSameType(currentType)) {
assert accessorClass.getSourceInfo().getFileSourceInfo()
.equals(currentType.getSourceInfo().getFileSourceInfo());
WrapperMarker marker = accessorClass.getMarker(WrapperMarker.class);
if (marker == null) {
marker = new WrapperMarker();
accessorClass.addMarker(marker);
}
JMethod wrapper = marker.getOrCreateWrapper(method, (JDefinedClass) accessorClass,
isSuper);
JMethodCall wrapperCall = null;
SourceInfo sourceInfo = x.getSourceInfo();
if (x instanceof JNewInstance) {
assert wrapper instanceof JConstructor;
wrapperCall =
new JNewInstance(sourceInfo, wrapper.getEnclosingType(), wrapper.getMethodId());
} else {
JMethodId wrapperId = wrapper.getMethodId();
// this.this$0.method(param) => $wrap<id>(this.this$0, param)
if (!method.isStatic() && !(wrapper instanceof JConstructor)) {
wrapperCall = new JMethodCall(sourceInfo, null, accessorClass, wrapperId,
wrapper.getType(), wrapperId.canBeVirtual());
JExpression instance = x.getInstance();
assert instance != null;
wrapperCall.addArg(instance);
} else {
wrapperCall = new JMethodCall(sourceInfo, x.getInstance(), accessorClass,
wrapperId, wrapper.getType(), wrapperId.canBeVirtual());
}
}
for (JExpression arg : x.getArgs()) {
wrapperCall.addArg(arg);
}
if (wrapper instanceof JConstructor) {
int numberOfParamToAdd = wrapper.getParams().size() - method.getParams().size();
for (int i = 0; i < numberOfParamToAdd; i++) {
wrapperCall.addArg(new JNullLiteral(sourceInfo));
}
}
assert wrapperCall.getArgs().size() == wrapper.getParams().size();
tr.append(new Replace(x, wrapperCall));
}
}
return super.visit(x);
}
@Override
public boolean visit(@Nonnull JDefinedClassOrInterface type) {
currentType = type;
tr = new TransformationRequest(type);
// Sort types and methods to make this visitor deterministic
for (JMethod method : methodOrdering.sortedCopy(type.getMethods())) {
if (!method.isNative() && !method.isAbstract()
&& filter.accept(InnerAccessorGenerator.class, method)) {
this.accept(method);
}
}
assert tr != null;
tr.commit();
for (JClassOrInterface innerType : typeOrdering.sortedCopy(type.getMemberTypes())) {
if (innerType instanceof JDefinedClassOrInterface) {
visit((JDefinedClassOrInterface) innerType);
}
}
return false;
}
}
@Nonnull
TypePackageAndMethodFormatter formatter = Jack.getLookupFormatter();
private int compareSourceInfo(@Nonnull JNode n1, @Nonnull JNode n2) {
return n1.getSourceInfo().getStartLine() - n2.getSourceInfo().getStartLine();
}
@Nonnull
private final Ordering<JMethod> methodOrdering = Ordering.from(new Comparator<JMethod>() {
@Override
public int compare(@Nonnull JMethod m1, @Nonnull JMethod m2) {
int compareSourceInfo = compareSourceInfo(m1, m2);
if (compareSourceInfo != 0) {
return compareSourceInfo;
}
return formatter.getName(m1).compareTo(formatter.getName(m2));
}
});
@Nonnull
private final Ordering<JClassOrInterface> typeOrdering =
Ordering.from(new Comparator<JClassOrInterface>() {
@Override
public int compare(@Nonnull JClassOrInterface t1, @Nonnull JClassOrInterface t2) {
int compareSourceInfo = compareSourceInfo((JNode) t1, (JNode) t2);
if (compareSourceInfo != 0) {
return compareSourceInfo;
}
return formatter.getName(t1).compareTo(formatter.getName(t2));
}
});
@Override
public synchronized void run(@Nonnull JDefinedClassOrInterface type) throws Exception {
// Start visit on outer types for a deterministic visit order.
if (type.getEnclosingType() != null) {
return;
}
// No need to visit types without inner classes.
if (type.getMemberTypes().isEmpty()) {
return;
}
new Visitor().accept(type);
}
/**
* @param type
* @return true if the instance is a superclass of type
*/
static boolean isSuperClassOf(JDefinedClass possibleSuper, JDefinedClass type) {
JDefinedClassOrInterface superClass = (JDefinedClassOrInterface) type.getSuperClass();
while (superClass != null) {
if (possibleSuper.isSameType(superClass)) {
return true;
}
superClass = (JDefinedClassOrInterface) superClass.getSuperClass();
}
return false;
}
}