/**
   * Returns a class which has a static method with the same name as 'method', whose body creates an
   * new instance of 'specimen' and invokes 'method' upon it via an invokevirtual instruction with
   * 'args' as function call parameters.
   *
   * <p>'returns' is a dummy return value that need only match 'methods' return type (it is only
   * used in the dummy class when compiling IV).
   */
  private Class invokeVirtualHarness(
      Class specimen, ConcreteMethod method, String returns, String... args) {
    Method cm =
        new ConcreteMethod(
            method.getReturnType(),
            method.getName(),
            "return " + returns + ";",
            method.getElements());
    Class stub = new Class(specimen.getName(), cm);

    String params = toJoinedString(args, ", ");

    ConcreteMethod sm =
        new ConcreteMethod(
            method.getReturnType(),
            method.getName(),
            String.format(
                "return (new %s()).%s(%s);", specimen.getName(), method.getName(), params),
            new AccessFlag("public"),
            new AccessFlag("static"));

    Class iv = new Class("IV_" + specimen.getName(), sm);

    iv.addCompilationDependency(stub);
    iv.addCompilationDependency(cm);

    return iv;
  }
  /**
   * Creates a class which calls target::method(args) via invokevirtual, compiles and loads both the
   * new class and 'target', and then invokes the method. If the returned value does not match
   * 'value' then a test failure is indicated.
   */
  public void assertInvokeVirtualEquals(
      Object value, Class target, ConcreteMethod method, String returns, String... args) {

    Compiler compiler = compilerLocal.get();
    compiler.setFlags(compilerFlags());

    Class iv = invokeVirtualHarness(target, method, returns, args);
    ClassLoader loader = compiler.compile(iv, target);

    assertStaticCallEquals(loader, iv, method.getName(), value);
    compiler.cleanup();
  }
  /**
   * Creates a class which calls target::method(args) via invokevirtual, compiles and loads both the
   * new class and 'target', and then invokes the method. If an exception of type 'exceptionType' is
   * not thrown, then a test failure is indicated.
   */
  public void assertThrows(
      java.lang.Class<?> exceptionType,
      Class target,
      ConcreteMethod method,
      String returns,
      String... args) {

    Compiler compiler = compilerLocal.get();
    compiler.setFlags(compilerFlags());

    Class iv = invokeVirtualHarness(target, method, returns, args);
    ClassLoader loader = compiler.compile(iv, target);

    java.lang.Class<?> cls = null;
    try {
      cls = java.lang.Class.forName(iv.getName(), true, loader);
    } catch (ClassNotFoundException e) {
    }
    assertNotNull(cls);

    java.lang.reflect.Method m = null;
    try {
      m = cls.getMethod(method.getName());
    } catch (NoSuchMethodException e) {
    }
    assertNotNull(m);

    try {
      m.invoke(null);
      fail("Exception should have been thrown");
    } catch (InvocationTargetException | IllegalAccessException e) {
      if (verboseLocal.get() == Boolean.TRUE) {
        System.out.println(e.getCause());
      }
      assertTrue(exceptionType.isAssignableFrom(e.getCause().getClass()));
    }
    compiler.cleanup();
  }