private static Statement makeSnapshotAsSubclass()

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;
      }
    };
  }