import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class LazyLoading {
  public static boolean loadLazy;

  public static void expect(boolean v) {
    if (! v) throw new RuntimeException();
  }

  private static File findClass(String name, File directory) {
    for (File file: directory.listFiles()) {
      if (file.isFile()) {
        if (file.getName().equals(name + ".class")) {
          return file;
        }
      } else if (file.isDirectory()) {
        File result = findClass(name, file);
        if (result != null) {
          return result;
        }
      }
    }
    return null;
  }

  private static byte[] read(File file) throws IOException {
    byte[] bytes = new byte[(int) file.length()];
    FileInputStream in = new FileInputStream(file);
    try {
      if (in.read(bytes) != (int) file.length()) {
        throw new RuntimeException();
      }
      return bytes;
    } finally {
      in.close();
    }
  }

  public static void main(String[] args) throws Exception {
    Class c = new MyClassLoader(LazyLoading.class.getClassLoader()).loadClass
      ("LazyLoading$Test");

    c.getMethod("test").invoke(null);
  }

  private static class MyClassLoader extends ClassLoader {
    public MyClassLoader(ClassLoader parent) {
      super(parent);
    }

    protected Class findClass(String name) throws ClassNotFoundException {
      try {
        return defineClass
          (name, read
           (LazyLoading.findClass
            (name, new File(System.getProperty("user.dir")))));
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
    }

    public Class loadClass(String name) throws ClassNotFoundException {
      if ("LazyLoading$Test".equals(name)) {
        return findClass(name);
      } else if ("LazyLoading$Lazy".equals(name)
                 || "LazyLoading$Interface".equals(name))
      {
        if (loadLazy) {
          return findClass(name);
        } else {
          throw new ClassNotFoundException(name);
        }
      } else {
        return super.loadClass(name);
      }
    }

    private Class defineClass(String name, byte[] bytes) {
      return defineClass(name, bytes, 0, bytes.length);
    }
  }

  public static class Test {
    public static void test() {
      doTest();
      loadLazy = true;
      doTest();
    }

    private static void doTest() {
      if (loadLazy) {
        // anewarray
        Lazy[] array = new Lazy[1];
        
        // new and invokespecial
        Object lazy = new Lazy();
        
        // checkcast
        array[0] = (Lazy) lazy;

        // instanceof
        expect(lazy instanceof Lazy);

        // invokeinterface
        expect(array[0].interfaceMethod() == 42);

        // invokestatic
        expect(Lazy.staticMethod() == 43);

        // invokevirtual
        expect(array[0].virtualMethod() == 44);

        // ldc
        expect(Lazy.class == lazy.getClass());

        // multianewarray
        Lazy[][] multiarray = new Lazy[5][6];
        multiarray[2][3] = array[0];
        expect(multiarray[2][3] == array[0]);

        // getfield
        expect(array[0].intField == 45);

        // getstatic
        expect(Lazy.intStaticField == 46);

        // putfield int
        array[0].intField = 47;
        expect(array[0].intField == 47);

        // putfield long
        array[0].longField = 48;
        expect(array[0].longField == 48);

        // putfield object
        Object x = new Object();
        array[0].objectField = x;
        expect(array[0].objectField == x);

        // putstatic int
        array[0].intStaticField = 49;
        expect(array[0].intStaticField == 49);

        // putstatic long
        array[0].longStaticField = 50;
        expect(array[0].longStaticField == 50);

        // putstatic object
        Object y = new Object();
        array[0].objectStaticField = y;
        expect(array[0].objectStaticField == y);
      }
    }
  }

  private interface Interface {
    public int interfaceMethod();
  }

  private static class Lazy implements Interface {
    public static int intStaticField = 46;
    public static long longStaticField;
    public static Object objectStaticField;

    public int intField = 45;
    public long longField;
    public Object objectField;

    public int interfaceMethod() {
      return 42;
    }

    public static int staticMethod() {
      return 43;
    }

    public int virtualMethod() {
      return 44;
    }
  }
}