in packages/stunner-editors/errai-codegen/src/main/java/org/jboss/errai/codegen/SnapshotMaker.java [240:423]
private static Statement makeSnapshotAsSubclass(
final Object o,
final MetaClass typeToSnapshot,
final MetaClass typeToExtend,
final MethodBodyCallback methodBodyCallback,
final Set<MetaClass> typesToRecurseOn,
final IdentityHashMap<Object, Statement> existingSnapshots,
final Set<Object> unfinishedSnapshots) {
if (o == null) {
return NullLiteral.INSTANCE;
}
if (!typeToSnapshot.isAssignableFrom(o.getClass())) {
throw new IllegalArgumentException(
"Given object (of type " + o.getClass().getName() +
") is not an instance of requested type to snapshot " + typeToSnapshot.getName());
}
if (logger.isDebugEnabled()) {
logger.debug("** Making snapshot of " + o);
logger.debug(" Existing snapshots: " + existingSnapshots);
}
final List<MetaMethod> sortedMethods = Arrays.asList(typeToExtend.getMethods());
Collections.sort(sortedMethods, new Comparator<MetaMethod>() {
@Override
public int compare(MetaMethod m1, MetaMethod m2) {
return m1.getName().compareTo(m2.getName());
}
});
logger.debug(" Creating a new statement");
return new Statement() {
String generatedCache;
/**
* We retain a mapping of return values to the methods that returned them,
* in case we need to provide diagnostic information when an exception is
* thrown.
*/
IdentityHashMap<Object, MetaMethod> methodReturnVals = new IdentityHashMap<Object, MetaMethod>();
@Override
public String generate(Context context) {
if (logger.isDebugEnabled()) {
logger.debug("++ Statement.generate() for " + o);
}
if (generatedCache != null) return generatedCache;
// create a subcontext and record the types we will allow the LiteralFactory to create automatic
// snapshots for.
final Context subContext = Context.create(context);
subContext.addLiteralizableMetaClasses(typesToRecurseOn);
final AnonymousClassStructureBuilder builder = ObjectBuilder.newInstanceOf(typeToExtend.getErased(), context)
.extend();
unfinishedSnapshots.add(o);
for (final MetaMethod method : sortedMethods) {
if (method.isFinal() || method.getName().equals("toString")) continue;
if (logger.isDebugEnabled()) {
logger.debug(" method " + method.getName());
logger.debug(" return type " + method.getReturnType());
}
if (methodBodyCallback != null) {
final Statement providedMethod = methodBodyCallback.generateMethodBody(method, o, builder);
if (providedMethod != null) {
logger.debug(" body provided by callback");
builder
.publicOverridesMethod(method.getName(), Parameter.of(method.getParameters()))
.append(providedMethod)
.finish();
continue;
}
}
if (method.getName().equals("equals") || method.getName().equals("hashCode")
|| method.getName().equals("clone") || method.getName().equals("finalize")) {
// we skip these if not provided by the callback
if (logger.isDebugEnabled()) {
logger.debug(" skipping special-case method " + method.getName());
}
continue;
}
if (method.getParameters().length > 0) {
throw new GenerationException("Method " + method + " in " + typeToSnapshot +
" takes parameters. Such methods must be handled by the MethodBodyCallback," +
" because they cannot be snapshotted.");
}
if (method.getReturnType().equals(MetaClassFactory.get(void.class))) {
builder.publicOverridesMethod(method.getName()).finish();
if (logger.isDebugEnabled()) {
logger.debug(" finished method " + method.getName());
}
continue;
}
try {
final Object retval = typeToExtend.asClass().getMethod(method.getName()).invoke(o);
methodReturnVals.put(retval, method);
if (logger.isDebugEnabled()) {
logger.debug(" retval=" + retval);
}
Statement methodBody;
if (existingSnapshots.containsKey(retval)) {
logger.debug(" using existing snapshot");
methodBody = existingSnapshots.get(retval);
}
else if (subContext.isLiteralizableClass(method.getReturnType().getErased())) {
if (unfinishedSnapshots.contains(retval)) {
throw new CyclicalObjectGraphException(unfinishedSnapshots);
}
// use Stmt.create(context) to pass the context along.
if (logger.isDebugEnabled()) {
logger.debug(" >> recursing for " + retval);
}
methodBody = Stmt.create(subContext).nestedCall(makeSnapshotAsSubclass(
retval, method.getReturnType(), method.getReturnType(),
methodBodyCallback, typesToRecurseOn, existingSnapshots, unfinishedSnapshots)).returnValue();
}
else {
logger.debug(" relying on literal factory");
methodBody = Stmt.load(retval).returnValue();
}
if (logger.isDebugEnabled()) {
logger.debug(" finished method " + method.getName());
}
builder.publicOverridesMethod(method.getName()).append(methodBody).finish();
existingSnapshots.put(retval, methodBody);
}
catch (final GenerationException e) {
e.appendFailureInfo("In attempt to snapshot return value of "
+ typeToExtend.getFullyQualifiedName() + "." + method.getName() + "()");
throw e;
}
catch (final RuntimeException e) {
throw e;
}
catch (final Exception e) {
throw new GenerationException("Failed to extract value for snapshot", e);
}
}
if (logger.isDebugEnabled()) {
logger.debug(" finished: " + builder);
}
try {
generatedCache = prettyPrintJava(builder.finish().toJavaString());
} catch (final NotLiteralizableException e) {
final MetaMethod m = methodReturnVals.get(e.getNonLiteralizableObject());
if (m != null) {
e.appendFailureInfo("This value came from method " +
m.getDeclaringClass().getFullyQualifiedNameWithTypeParms() + "." + m.getName() +
", which has return type " + m.getReturnType());
}
throw e;
} catch (final GenerationException e) {
e.appendFailureInfo("While generating a snapshot of " + o.toString() +
" (actual type: " + o.getClass().getName() +
"; type to extend: " + typeToExtend.getFullyQualifiedName() + ")");
throw e;
}
unfinishedSnapshots.remove(o);
return generatedCache;
}
@Override
public MetaClass getType() {
return typeToExtend;
}
};
}