/**
* Copyright (C) 2010 Hal Hildebrand. All rights reserved.
*
* This file is part of the Prime Mover Event Driven Simulation Framework.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.hellblazer.primeMover.soot;
import static com.hellblazer.primeMover.soot.util.Utils.getEntityInterfaces;
import static com.hellblazer.primeMover.soot.util.Utils.isMarkedBlocking;
import static com.hellblazer.primeMover.soot.util.Utils.isMarkedNonEvent;
import static com.hellblazer.primeMover.soot.util.Utils.markBlocking;
import static com.hellblazer.primeMover.soot.util.Utils.markTransformed;
import static java.util.Arrays.asList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Logger;
import soot.ArrayType;
import soot.BooleanType;
import soot.Immediate;
import soot.IntType;
import soot.Local;
import soot.Modifier;
import soot.PrimType;
import soot.Scene;
import soot.SootClass;
import soot.SootField;
import soot.SootMethod;
import soot.Type;
import soot.Unit;
import soot.UnitBox;
import soot.VoidType;
import soot.jimple.IntConstant;
import soot.jimple.Jimple;
import soot.jimple.NullConstant;
import soot.jimple.StringConstant;
import com.hellblazer.primeMover.soot.util.MethodHelper;
/**
* Generates the subclass which will implement the mechanics of the event
* processing logic for entities
*
* @author <a href="mailto:hal.hildebrand@gmail.com">Hal Hildebrand</a>
*
*/
public class EntityGenerator {
private static Logger log = Logger.getLogger(EntityGenerator.class.getCanonicalName());
private static final String INIT = "<init>";
private static final String NO_ARG_CONSTRUCTOR_SIGNATURE = "void <init>()";
private static final String FRAMEWORK_POST_EVENT_SIGNATURE = "void postEvent(com.hellblazer.primeMover.runtime.EntityReference,int,java.lang.Object[])";
private static final String FRAMEWORK_POST_CONTINUING_EVENT_SIGNATURE = "java.lang.Object postContinuingEvent(com.hellblazer.primeMover.runtime.EntityReference,int,java.lang.Object[])";
private static final String CLINIT = "<clinit>";
public static final String CONTROLLER_FIELD = "__controller";
public static final String SIGNATURE_FOR_METHOD_NAME = "__signatureFor";
public static final String INITIALIZE_METHOD_NAME = "__initialize";
public static final String INITIALIZED_FIELD = "__initialized";
public static final String EVENT_MAP = "__EVENT_MAP";
public static final String INVOKE_METHOD_NAME = "__invoke";
public static final String GENERATED_ENTITY_SUFFIX = "$entity$gen";
private final boolean validate;
private final SootClass base;
private final SootClass entity;
private final Map<SootMethod, Integer> invokeMap = new HashMap<SootMethod, Integer>();
private final Map<Integer, SootMethod> inverseInvokeMap = new TreeMap<Integer, SootMethod>();
private final SootClass framework = Scene.v().loadClass("com.hellblazer.primeMover.runtime.Framework",
SootClass.SIGNATURES);
private final SootClass devi = Scene.v().loadClass("com.hellblazer.primeMover.runtime.Devi",
SootClass.SIGNATURES);
private final SootClass object = Scene.v().loadClass(Object.class.getCanonicalName(),
SootClass.SIGNATURES);
private final SootClass string = Scene.v().loadClass(String.class.getCanonicalName(),
SootClass.SIGNATURES);
private final Type stringArrayType = ArrayType.v(string.getType(),
1);
private final SootClass entityReference = Scene.v().loadClass("com.hellblazer.primeMover.runtime.EntityReference",
SootClass.SIGNATURES);
private final SootClass noSuchMethodError = Scene.v().loadClass(NoSuchMethodError.class.getCanonicalName(),
SootClass.SIGNATURES);
public EntityGenerator(List<SootClass> generated, SootClass base) {
this(generated, base, false);
}
public EntityGenerator(List<SootClass> generated, SootClass base,
boolean validate) {
this.base = base;
this.validate = validate;
entity = new SootClass(base.getJavaPackageName() + "."
+ base.getJavaStyleName()
+ GENERATED_ENTITY_SUFFIX, Modifier.PUBLIC);
Scene.v().addClass(entity);
generated.add(entity);
entity.setSuperclass(base);
populateFields();
}
protected void constructInvokeMap() {
HashSet<String> mapped = new HashSet<String>();
for (SootMethod method : object.getMethods()) {
if (method.isPublic() && !method.isStatic() && !isInit(method)) {
mapped.add(method.getSubSignature());
}
}
int index = 0;
Collection<SootClass> entityInterfaces = getEntityInterfaces(base);
if (entityInterfaces.isEmpty()) {
index = gatherAllMethods(mapped, index);
} else {
for (SootClass iFace : entityInterfaces) {
index = gatherMethodsForInterface(iFace, mapped, index);
}
}
for (SootMethod method : inverseInvokeMap.values()) {
if (method.getReturnType() != VoidType.v()
&& !isMarkedBlocking(method)) {
markBlocking(method);
log.info(String.format("Inferred blocking status of %1s. Marking method as @Blocking",
method));
}
}
}
private SootMethod findMethod(String subSignature) {
SootClass current = base;
while (base != null) {
if (current.declaresMethod(subSignature)) {
return current.getMethod(subSignature);
}
current = current.hasSuperclass() ? current.getSuperclass() : null;
}
throw new IllegalStateException(
String.format("Cannot find concrete method %s in %s",
subSignature, base));
}
private int gatherAllMethods(Set<String> mapped, int index) {
SootClass current = base;
while (current != null && current != object) {
for (SootMethod method : current.getMethods()) {
if (!mapped.contains(method.getSubSignature())
&& isEvent(method)) {
invokeMap.put(method, index);
inverseInvokeMap.put(index++, method);
mapped.add(method.getSubSignature());
}
}
current = current.getSuperclass();
}
return index;
}
private int gatherMethodsForInterface(SootClass iFace, Set<String> mapped,
int index) {
for (SootMethod method : iFace.getMethods()) {
String subSignature = method.getSubSignature();
if (!mapped.contains(subSignature) && !isMarkedNonEvent(method)) {
SootMethod concrete = findMethod(subSignature);
if (!isMarkedNonEvent(concrete)) {
invokeMap.put(concrete, index);
inverseInvokeMap.put(index++, concrete);
mapped.add(subSignature);
}
}
}
return index;
}
/**
* Generate the static class initialization method. This method initializes
* the map of event method signatures to their ordinal number.
*
* Method body:
*
* static { __EVENT_MAP = new HashMap<Integer, String>(); __EVENT_MAP.put(0,
* "void event1()"); ...
*
* __EVENT_MAP.put(N, "void eventN()"); }
*
* @return
*/
protected SootMethod generateClassInitMethod() {
MethodHelper helper = new MethodHelper(entity, CLINIT,
Modifier.STATIC
| Modifier.PRIVATE);
Local map = helper.newLocal("map", stringArrayType);
helper.assign(map,
Jimple.v().newNewArrayExpr(string.getType(),
IntConstant.v(inverseInvokeMap.size())));
helper.assignStaticVariable(EVENT_MAP, map);
for (Map.Entry<Integer, SootMethod> entry : inverseInvokeMap.entrySet()) {
helper.assign(Jimple.v().newArrayRef(map,
IntConstant.v(entry.getKey())),
StringConstant.v(getSignature(entry.getValue())));
}
helper.returnVoid();
entity.addMethod(helper.getMethod());
if (validate) {
helper.validate();
}
return helper.getMethod();
}
protected SootMethod generateConstructor(SootMethod constructor,
SootMethod initializeMethod) {
@SuppressWarnings("unchecked")
MethodHelper helper = new MethodHelper(entity, constructor.getName(),
constructor.getParameterTypes(),
VoidType.v(),
constructor.getModifiers());
helper.invoke(Jimple.v().newSpecialInvokeExpr(helper.thisLocal(),
constructor.makeRef(),
helper.getParameters()));
helper.invoke(Jimple.v().newVirtualInvokeExpr(helper.thisLocal(),
initializeMethod.makeRef()));
helper.returnVoid();
entity.addMethod(helper.getMethod());
if (validate) {
helper.validate();
}
return helper.getMethod();
}
protected List<SootMethod> generateConstructors(SootMethod initializeMethod) {
ArrayList<SootMethod> constructors = new ArrayList<SootMethod>();
for (SootMethod method : base.getMethods()) {
if (INIT.equals(method.getName())) {
constructors.add(generateConstructor(method, initializeMethod));
}
}
return constructors;
}
public void generateEntity() {
constructInvokeMap();
generateClassInitMethod();
SootMethod initializeMethod = generateInitializeMethod();
generateSignatureForMethod();
generateConstructors(initializeMethod);
generateInvokeMethod();
for (SootMethod event : invokeMap.keySet()) {
generateEvent(event, initializeMethod);
}
markTransformed(base, this, "Generated entity proxy subclass");
}
protected SootMethod generateEvent(SootMethod event,
SootMethod initializeMethod) {
@SuppressWarnings("unchecked")
MethodHelper helper = new MethodHelper(entity, event.getName(),
event.getParameterTypes(),
event.getReturnType(),
event.getModifiers());
Local initialized = helper.newLocal("initialized", BooleanType.v());
helper.assignInstanceVariableTo(initialized, INITIALIZED_FIELD);
UnitBox target = helper.generateIf(Jimple.v().newEqExpr(initialized,
IntConstant.v(1)));
helper.invoke(Jimple.v().newVirtualInvokeExpr(helper.thisLocal(),
initializeMethod.makeRef()));
Local controller = helper.newLocal("controller", devi.getType());
helper.assignInstanceVariableTo(controller, CONTROLLER_FIELD);
target.setUnit(helper.getLast());
Local arguments = helper.newLocal("arguments",
ArrayType.v(object.getType(), 1));
if (event.getParameterCount() == 0) {
helper.assign(arguments, NullConstant.v());
} else {
helper.loadArgumentsArray(arguments);
}
if (isMarkedBlocking(event)) {
Local continuationReturnValue = helper.newLocal("continuationReturnValue",
object.getType());
helper.assign(continuationReturnValue,
Jimple.v().newVirtualInvokeExpr(controller,
devi.getMethod(FRAMEWORK_POST_CONTINUING_EVENT_SIGNATURE).makeRef(),
asList(helper.thisLocal(),
IntConstant.v(invokeMap.get(event)),
arguments)));
helper.returnUnboxed(event.getReturnType(), continuationReturnValue);
} else {
helper.invoke(Jimple.v().newVirtualInvokeExpr(controller,
devi.getMethod(FRAMEWORK_POST_EVENT_SIGNATURE).makeRef(),
asList(helper.thisLocal(),
IntConstant.v(invokeMap.get(event)),
arguments)));
helper.returnVoid();
}
entity.addMethod(helper.getMethod());
if (validate) {
helper.validate();
}
return helper.getMethod();
}
/**
* Construct the method which initializes the entity.
*
* method body:
*
* private synchronized void __initialize() { if (__initialized) { return; }
* __controller = Framework.getController(); __id = UUID.randomUUID();
* __initialized = true; }
*
* @return
*/
protected SootMethod generateInitializeMethod() {
@SuppressWarnings("unchecked")
MethodHelper helper = new MethodHelper(entity, INITIALIZE_METHOD_NAME,
Collections.EMPTY_LIST,
VoidType.v(),
Modifier.SYNCHRONIZED
| Modifier.PRIVATE);
/*
* if (__initialized) { return; }
*/
Local initializedLocal = helper.newLocal("initialized", BooleanType.v());
helper.assignInstanceVariableTo(initializedLocal, INITIALIZED_FIELD);
UnitBox target = helper.generateIf(Jimple.v().newEqExpr(initializedLocal,
IntConstant.v(0)));
helper.returnVoid();
/*
* __controller = Framework.getController();
*/
SootMethod getController = framework.getMethod("getController",
Collections.emptyList(),
devi.getType());
Local controllerLocal = helper.newLocal("controller", devi.getType());
helper.assign(controllerLocal,
Jimple.v().newStaticInvokeExpr(getController.makeRef()));
target.setUnit(helper.getLast());
helper.assignInstanceVariable(CONTROLLER_FIELD, controllerLocal);
/*
* __initialized = true; return;
*/
helper.assignInstanceVariable(INITIALIZED_FIELD, IntConstant.v(1));
helper.returnVoid();
entity.addMethod(helper.getMethod());
if (validate) {
helper.validate();
}
return helper.getMethod();
}
protected Unit generateInvoke(SootMethod event, Local arguments,
MethodHelper helper, String tempPrefix) {
Unit first = Jimple.v().newNopStmt();
helper.add(first);
ArrayList<Local> castArguments = new ArrayList<Local>();
for (int i = 0; i < event.getParameterCount(); i++) {
Local rawArg = helper.newLocal(tempPrefix + "-raw" + i,
object.getType());
Type parameterType = event.getParameterType(i);
Local arg = helper.newLocal(tempPrefix + i, parameterType);
castArguments.add(arg);
// Object rawArg = arguments[i];
helper.assign(rawArg,
Jimple.v().newArrayRef(arguments, IntConstant.v(i)));
if (parameterType instanceof PrimType) {
// unbox instructions
helper.unbox((PrimType) parameterType, arg, rawArg);
} else {
// <Type> arg = (<Type>) rawArg;
helper.assign(arg,
Jimple.v().newCastExpr(rawArg, parameterType));
}
}
if (event.getReturnType().equals(VoidType.v())) {
helper.invoke(Jimple.v().newSpecialInvokeExpr(helper.thisLocal(),
event.makeRef(),
castArguments));
helper.returnValue(NullConstant.v());
} else {
Local result = helper.newLocal("result", event.getReturnType());
helper.assign(result,
Jimple.v().newSpecialInvokeExpr(helper.thisLocal(),
event.makeRef(),
castArguments));
helper.returnBoxed(event.getReturnType(), result);
}
return first;
}
protected SootMethod generateInvokeMethod() {
MethodHelper helper = new MethodHelper(
entity,
INVOKE_METHOD_NAME,
asList("eventOrdinal",
"arguments"),
asList(IntType.v(),
ArrayType.v(object.getType(),
1)),
object.getType(),
Modifier.PUBLIC);
UnitBox target = Jimple.v().newStmtBox(null);
helper.add(Jimple.v().newGotoStmt(target));
/**
* switch (signatureOrdinal) { case 1: { result = this.event((ArgType)
* arguments[0], (AnotherArgType) arguments[1]...); break; } ...
* default: { throw new NoSuchMethodError(); } }
*/
ArrayList<Immediate> lookupValues = new ArrayList<Immediate>();
ArrayList<Unit> targets = new ArrayList<Unit>();
String tempPrefix = "invoke$tmp$";
for (Map.Entry<Integer, SootMethod> entry : inverseInvokeMap.entrySet()) {
lookupValues.add(IntConstant.v(entry.getKey()));
targets.add(generateInvoke(entry.getValue(),
helper.getParameter(1), helper,
tempPrefix + entry.getKey().intValue()));
}
Local nsmInstance = helper.newLocal("nsmInstance",
noSuchMethodError.getType());
// nsmInstance = new NoSuchMethodError();
helper.assign(nsmInstance,
Jimple.v().newNewExpr(noSuchMethodError.getType()));
Unit defaultTarget = helper.getLast();
helper.invoke(Jimple.v().newSpecialInvokeExpr(nsmInstance,
noSuchMethodError.getMethod(NO_ARG_CONSTRUCTOR_SIGNATURE).makeRef(),
Collections.EMPTY_LIST));
// throw nsmInstance;
helper.add(Jimple.v().newThrowStmt(nsmInstance));
helper.add(Jimple.v().newLookupSwitchStmt(helper.getParameter(0),
lookupValues, targets,
defaultTarget));
target.setUnit(helper.getLast());
entity.addMethod(helper.getMethod());
if (validate) {
helper.validate();
}
return helper.getMethod();
}
protected SootMethod generateSignatureForMethod() {
MethodHelper helper = new MethodHelper(entity,
SIGNATURE_FOR_METHOD_NAME,
asList(IntType.v()),
string.getType(),
Modifier.PUBLIC);
Local eventMap = helper.newLocal("eventMap", stringArrayType);
helper.assignStaticVariableTo(eventMap, EVENT_MAP);
Local signature = helper.newLocal("signature", string.getType());
helper.assign(signature,
Jimple.v().newArrayRef(eventMap, helper.getParameter(0)));
helper.returnValue(signature);
entity.addMethod(helper.getMethod());
if (validate) {
helper.validate();
}
return helper.getMethod();
}
public SootClass getBase() {
return base;
}
public SootClass getEntity() {
return entity;
}
protected String getSignature(SootMethod method) {
StringBuffer buffer = new StringBuffer();
buffer.append("<" + Scene.v().quotedNameOf(base.getName()) + ": ");
buffer.append(method.getSubSignature());
buffer.append(">");
return buffer.toString();
}
private boolean isEvent(SootMethod method) {
return method.isPublic() && method.isConcrete() && !method.isStatic()
&& !isInit(method) && !isMarkedNonEvent(method);
}
private boolean isInit(SootMethod method) {
return method.getName().equals(INIT) || method.getName().equals(CLINIT);
}
protected void populateFields() {
// private static HashMap __EVENT_METHOD_CACHE;
entity.addField(new SootField(EVENT_MAP, stringArrayType,
Modifier.STATIC | Modifier.PRIVATE));
// private Kalachakra __controller;
entity.addField(new SootField(CONTROLLER_FIELD, devi.getType(),
Modifier.PRIVATE));
// private boolean __initialized;
entity.addField(new SootField(INITIALIZED_FIELD, BooleanType.v(),
Modifier.PRIVATE));
entity.addInterface(entityReference);
}
}