blob: 1c401d97ca8a028308c908d4d672a2c6d9b2bcd4 [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.android.jack.ir.ast.JBlock;
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.JExpression;
import com.android.jack.ir.ast.JMethod;
import com.android.jack.ir.ast.JMethodBody;
import com.android.jack.ir.ast.JMethodCall;
import com.android.jack.ir.ast.JMethodId;
import com.android.jack.ir.ast.JMethodIdWide;
import com.android.jack.ir.ast.JModifier;
import com.android.jack.ir.ast.JParameter;
import com.android.jack.ir.ast.JParameterRef;
import com.android.jack.ir.ast.JPrimitiveType.JPrimitiveTypeEnum;
import com.android.jack.ir.ast.JReturnStatement;
import com.android.jack.ir.ast.JThis;
import com.android.jack.ir.ast.JThisRef;
import com.android.jack.ir.ast.JType;
import com.android.jack.ir.ast.MethodKind;
import com.android.jack.ir.formatter.IdentifierFormatter;
import com.android.jack.ir.sourceinfo.SourceInfo;
import com.android.jack.util.NamingTools;
import com.android.sched.item.AbstractComponent;
import com.android.sched.item.ComposedOf;
import com.android.sched.item.Description;
import com.android.sched.item.Name;
import com.android.sched.marker.Marker;
import com.android.sched.marker.ValidOn;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
/**
* This marker indicates that a method has an associated wrapper.
*/
@ValidOn(JDefinedClass.class)
@Description("This marker indicates that a method has an associated wrapper.")
public class WrapperMarker implements Marker {
@Nonnull
private static final String WRAPPER_PREFIX = NamingTools.getNonSourceConflictingName("wrap");
@Nonnull
private final HashMap<MethodCallDescriptor, JMethod> wrappers =
new HashMap<MethodCallDescriptor, JMethod>();
private static class MethodCallDescriptor {
@Nonnull
private final JMethod method;
private final boolean isSuper;
@Nonnull
private final JClassOrInterface mthCallReceiverType;
private MethodCallDescriptor(@Nonnull JMethod method, boolean isSuper,
@Nonnull JClassOrInterface mthCallReceiverType) {
this.method = method;
this.isSuper = isSuper;
this.mthCallReceiverType = mthCallReceiverType;
}
@Override
public final boolean equals(@CheckForNull Object obj) {
if (obj instanceof MethodCallDescriptor) {
MethodCallDescriptor toCompare = (MethodCallDescriptor) obj;
return method.equals(toCompare.method)
&& isSuper == toCompare.isSuper
&& mthCallReceiverType.isSameType(toCompare.mthCallReceiverType);
}
return false;
}
@Override
public final int hashCode() {
int hashCode = method.hashCode() ^ mthCallReceiverType.hashCode();
return 31 * hashCode + Boolean.valueOf(isSuper).hashCode();
}
}
@CheckForNull
private JMethod getWrapper(@Nonnull JMethod method, boolean isStaticDispatchOnly,
@Nonnull JClassOrInterface mthCallReceiverType) {
MethodCallDescriptor descriptor =
new MethodCallDescriptor(method, isStaticDispatchOnly, mthCallReceiverType);
return wrappers.get(descriptor);
}
private boolean hasSameKeyWithoutReceiverType(@Nonnull MethodCallDescriptor newKey) {
for (MethodCallDescriptor mcd : wrappers.keySet()) {
if (mcd.method.equals(newKey.method)
&& mcd.isSuper == newKey.isSuper) {
return true;
}
}
return false;
}
private void addWrapper(@Nonnull JMethod method, @Nonnull JMethod wrapper,
boolean isStaticDispatchOnly, @Nonnull JClassOrInterface mthCallReceiverType) {
MethodCallDescriptor descriptor =
new MethodCallDescriptor(method, isStaticDispatchOnly, mthCallReceiverType);
// Check if Jack generates an additional accessor due to receiver type.
if (hasSameKeyWithoutReceiverType(descriptor)) {
throw new AssertionError();
}
assert !wrappers.containsKey(descriptor);
wrappers.put(descriptor, wrapper);
}
@Nonnull
Collection<JMethod> getAllWrappers() {
return wrappers.values();
}
@Override
@Nonnull
public Marker cloneIfNeeded() {
throw new AssertionError("Not yet supported");
}
@Name("InnerAccessorWrapper")
@Description("All JNodes created for a wrapper allowing to access an inner method.")
@ComposedOf({JMethod.class,
JConstructor.class,
JThisRef.class,
JParameter.class,
JParameterRef.class,
JMethodCall.class,
JMethodBody.class,
JReturnStatement.class,
JBlock.class})
static class InnerAccessorWrapper implements AbstractComponent {
}
@Nonnull
// TODO(delphinemartin): Warning: this is not thread-safe
JMethod getOrCreateWrapper(@Nonnull JMethod method,
@Nonnull JDefinedClass accessorClass,
boolean isSuper,
@Nonnull JClassOrInterface mthCallReceiverType) {
// $wrap<id>($param) {
// return method($param);
// }
JMethod wrapper = getWrapper(method, isSuper, mthCallReceiverType);
if (wrapper == null) {
SourceInfo sourceInfo = SourceInfo.UNKNOWN;
boolean isConstructor = method instanceof JConstructor;
if (isConstructor) {
wrapper = new JConstructor(sourceInfo, accessorClass, JModifier.SYNTHETIC);
} else {
String wrapperName = WRAPPER_PREFIX;
// It is a temporary deterministic name that will be replace by an index into
// InnerAccessorAdder
wrapperName += IdentifierFormatter.getFormatter().getName(mthCallReceiverType)
+ IdentifierFormatter.getFormatter().getName(method) + isSuper;
wrapper =
new JMethod(sourceInfo,
new JMethodId(new JMethodIdWide(wrapperName, MethodKind.STATIC),
method.getType()),
accessorClass,
JModifier.SYNTHETIC | JModifier.STATIC);
}
JExpression instance = null;
JMethodIdWide id = wrapper.getMethodIdWide();
if (isConstructor) {
JThis jThis = wrapper.getThis();
assert jThis != null;
instance = jThis.makeRef(sourceInfo);
} else if (!method.isStatic()){
JParameter thisParam =
new JParameter(sourceInfo, InnerAccessorGenerator.THIS_PARAM_NAME, accessorClass,
JModifier.FINAL | JModifier.SYNTHETIC, wrapper);
wrapper.addParam(thisParam);
id.addParam(accessorClass);
instance = thisParam.makeRef(sourceInfo);
}
JMethodIdWide calledMethodId = method.getMethodIdWide();
JMethodCall methodCall = new JMethodCall(sourceInfo, instance, mthCallReceiverType,
calledMethodId,
method.getType(), calledMethodId.canBeVirtual() && !isSuper /* isVirtualDispatch */);
for (JParameter param : method.getParams()) {
JType paramType = param.getType();
JParameter newParam = new JParameter(sourceInfo, param.getName(), paramType,
param.getModifier(), wrapper);
wrapper.addParam(newParam);
id.addParam(paramType);
methodCall.addArg(newParam.makeRef(sourceInfo));
}
if (isConstructor) {
while (constructorExists((JConstructor) wrapper, accessorClass)) {
JParameter newParam = new JParameter(
sourceInfo, InnerAccessorGenerator.THIS_PARAM_NAME + wrapper.getParams().size(),
accessorClass, JModifier.SYNTHETIC, wrapper);
wrapper.addParam(newParam);
id.addParam(accessorClass);
}
}
JBlock bodyBlock = new JBlock(sourceInfo);
JMethodBody body = new JMethodBody(sourceInfo, bodyBlock);
assert methodCall.getArgs().size() == methodCall.getMethodId().getParamTypes().size();
if (method.getType() == JPrimitiveTypeEnum.VOID.getType()) {
bodyBlock.addStmt(methodCall.makeStatement());
bodyBlock.addStmt(new JReturnStatement(sourceInfo, null));
} else {
bodyBlock.addStmt(new JReturnStatement(sourceInfo, methodCall));
}
wrapper.setBody(body);
addWrapper(method, wrapper, isSuper, mthCallReceiverType);
}
return wrapper;
}
/**
* Searches in the tree and in the wrapper that will be added
* @param wrapper
* @param accessorClass
* @return true if the constructor already exists
*/
private boolean constructorExists(
@Nonnull JConstructor wrapper, @Nonnull JDefinedClass accessorClass) {
for (JMethod method : accessorClass.getMethods()) {
if (method instanceof JConstructor && hasSameArgumentType(wrapper, (JConstructor) method)) {
return true;
}
}
for (JMethod method : getAllWrappers()) {
if (method instanceof JConstructor && hasSameArgumentType(wrapper, (JConstructor) method)) {
return true;
}
}
return false;
}
private boolean hasSameArgumentType(@Nonnull JMethod wrapper, @Nonnull JConstructor method) {
List<JParameter> wrapperParams = wrapper.getParams();
List<JParameter> methodParams = method.getParams();
int size = wrapperParams.size();
if (size != methodParams.size()) {
return false;
}
for (int i = 0; i < size; i++) {
if (!wrapperParams.get(i).getType().isSameType(methodParams.get(i).getType())) {
return false;
}
}
return true;
}
}