/*
* Copyright 2016, Stuart Douglas, and individual contributors as indicated
* by the @authors tag.
*
* 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 org.fakereplace.manip;
import java.util.Map;
import java.util.Set;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.Bytecode;
import javassist.bytecode.ClassFile;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.Opcode;
import org.fakereplace.manip.data.SubclassVirtualCallData;
import org.fakereplace.manip.util.ManipulationDataStore;
import org.fakereplace.manip.util.ManipulationUtils;
import org.fakereplace.runtime.VirtualDelegator;
import org.fakereplace.util.DescriptorUtils;
/**
* this manipulator adds code that looks like:
* <p>
* if(!this.getClass().getName().equals("CurrentClass")) //if this is a subclass
* {
* if(org.fakereplace.runtime.VirtualDelegator.contains(this,methodName,
* methodDescriptor))
* {
* return org.fakereplace.runtime.VirtualDelegator.run(this,methodName,
* methodDescriptor));
* }
* }
* <p>
* to a class
*
* @author stuart
*/
public class SubclassVirtualCallManipulator implements ClassManipulator {
private final ManipulationDataStore<SubclassVirtualCallData> data = new ManipulationDataStore<SubclassVirtualCallData>();
public void addClassData(String className, ClassLoader classLoader, String parentClassName, ClassLoader parentClassLoader, String methodName, String methodDesc) {
data.add(parentClassName, new SubclassVirtualCallData(parentClassLoader, parentClassName, methodName, methodDesc));
VirtualDelegator.add(classLoader, className, methodName, methodDesc);
}
public void clearRewrites(String className, ClassLoader classLoader) {
// we don't need to clear them. This is handled by clearing the data in
// VirtualDelegator
VirtualDelegator.clear(classLoader, className);
}
public boolean transformClass(ClassFile file, ClassLoader loader, boolean modifiableClass, final Set<MethodInfo> modifiedMethods) {
boolean modified = false;
Map<String, Set<SubclassVirtualCallData>> loaderData = data.getManipulationData(loader);
if (loaderData.containsKey(file.getName())) {
Set<SubclassVirtualCallData> d = loaderData.get(file.getName());
for (SubclassVirtualCallData s : d) {
for (Object m : file.getMethods()) {
MethodInfo method = (MethodInfo) m;
if (method.getName().equals(s.getMethodName()) && method.getDescriptor().equals(s.getMethodDesc())) {
modified = true;
// we have the method
// lets append our code to the top
// first create the stuff inside the coditionals
Bytecode run = new Bytecode(file.getConstPool());
run.add(Opcode.ALOAD_0);
run.addLdc(method.getName());
run.addLdc(method.getDescriptor());
String[] params = DescriptorUtils.descriptorStringToParameterArray(method.getDescriptor());
int count = 1;
for (int i = 0; i < params.length; ++i) {
if (params[i].length() > 1) {
run.addAload(count);
} else if (params[i].equals("I") || params[i].equals("Z") || params[i].equals("S") || params[i].equals("B")) {
run.addIload(count);
} else if (params[i].equals("F")) {
run.addFload(count);
} else if (params[i].equals("J")) {
run.addLload(count);
count++;
} else if (params[i].equals("D")) {
run.addDload(count);
count++;
}
count++;
}
ManipulationUtils.pushParametersIntoArray(run, method.getDescriptor());
run.addInvokestatic(VirtualDelegator.class.getName(), "run", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;");
ManipulationUtils.MethodReturnRewriter.addReturnProxyMethod(method.getDescriptor(), run);
Bytecode cd = new Bytecode(file.getConstPool());
cd.add(Opcode.ALOAD_0);
cd.addLdc(file.getName());
cd.addLdc(method.getName());
cd.addLdc(method.getDescriptor());
cd.addInvokestatic(VirtualDelegator.class.getName(), "contains", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z");
cd.add(Opcode.IFEQ); // if contains is true
ManipulationUtils.add16bit(cd, run.getSize() + 3);
Bytecode b = new Bytecode(file.getConstPool());
// this.getClass()
b.add(Opcode.ALOAD_0);
b.addInvokevirtual("java.lang.Object", "getClass", "()Ljava/lang/Class;");
b.addInvokevirtual("java.lang.Class", "getName", "()Ljava/lang/String;");
// now we have the class name on the stack
// push the class being manipulateds name onto the stack
b.addLdc(file.getName());
b.addInvokevirtual("java.lang.Object", "equals", "(Ljava/lang/Object;)Z");
// now we have a boolean on top of the stack
b.add(Opcode.IFNE); // if true jump
ManipulationUtils.add16bit(b, run.getSize() + cd.getSize() + 3);
try {
method.getCodeAttribute().iterator().insert(run.get());
method.getCodeAttribute().iterator().insert(cd.get());
method.getCodeAttribute().iterator().insert(b.get());
modifiedMethods.add(method);
} catch (BadBytecode e) {
e.printStackTrace();
}
}
}
}
}
return modified;
}
}