content: /*
   * Copyright (C) 2008 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  import java.lang.reflect.Constructor;
  import java.lang.reflect.Method;
  
  /**
   * Class loader test.
   */
  public class Main {
      /**
       * Main entry point.
       */
      public static void main(String[] args) throws Exception {
          FancyLoader loader;
  
          loader = new FancyLoader(ClassLoader.getSystemClassLoader());
          //System.out.println("SYSTEM: " + ClassLoader.getSystemClassLoader());
          //System.out.println("ALTERN: " + loader);
  
          /*
           * This statement has no effect on this program, but it can
           * change the point where a LinkageException is thrown in
           * testImplement().  When this is present the "reference
           * implementation" throws an exception from Class.newInstance(),
           * when it's absent the exception is deferred until the first time
           * we call a method that isn't actually implemented.
           *
           * This isn't the class that fails -- it's a class with the same
           * name in the "fancy" class loader --  but the VM thinks it has a
           * reference to one of these; presumably the difference is that
           * without this the VM finds itself holding a reference to an
           * instance of an uninitialized class.
           */
          System.out.println("base: " + DoubledImplement.class);
          System.out.println("base2: " + DoubledImplement2.class);
  
          /*
           * Run tests.
           */
          testAccess1(loader);
          testAccess2(loader);
          testAccess3(loader);
  
          testExtend(loader);
          testExtendOkay(loader);
          testInterface(loader);
          testAbstract(loader);
          testImplement(loader);
          testIfaceImplement(loader);
  
          testSeparation();
  
          testClassForName();
  
          testNullClassLoader();
      }
  
      static void testNullClassLoader() {
          try {
              /* this is the "alternate" DEX/Jar file */
              String DEX_FILE = System.getenv("DEX_LOCATION") + "/068-classloader-ex.jar";
              /* on Dalvik, this is a DexFile; otherwise, it's null */
              Class mDexClass = Class.forName("dalvik.system.DexFile");
              Constructor ctor = mDexClass.getConstructor(new Class[] {String.class});
              Object mDexFile = ctor.newInstance(DEX_FILE);
              Method meth = mDexClass.getMethod("loadClass",
                      new Class[] { String.class, ClassLoader.class });
              Object klass = meth.invoke(mDexFile, "Mutator", null);
              if (klass == null) {
                  throw new AssertionError("loadClass with nullclass loader failed");
              }
          } catch (Exception e) {
              System.out.println(e);
          }
          System.out.println("Loaded class into null class loader");
      }
  
      static void testSeparation() {
          FancyLoader loader1 = new FancyLoader(ClassLoader.getSystemClassLoader());
          FancyLoader loader2 = new FancyLoader(ClassLoader.getSystemClassLoader());
  
          try {
              Class target1 = loader1.loadClass("MutationTarget");
              Class target2 = loader2.loadClass("MutationTarget");
  
              if (target1 == target2) {
                  throw new RuntimeException("target1 should not be equal to target2");
              }
  
              Class mutator1 = loader1.loadClass("Mutator");
              Class mutator2 = loader2.loadClass("Mutator");
  
              if (mutator1 == mutator2) {
                  throw new RuntimeException("mutator1 should not be equal to mutator2");
              }
  
              runMutator(mutator1, 1);
  
              int value = getMutationTargetValue(target1);
              if (value != 1) {
                  throw new RuntimeException("target 1 has unexpected value " + value);
              }
              value = getMutationTargetValue(target2);
              if (value != 0) {
                  throw new RuntimeException("target 2 has unexpected value " + value);
              }
  
              runMutator(mutator2, 2);
  
              value = getMutationTargetValue(target1);
              if (value != 1) {
                  throw new RuntimeException("target 1 has unexpected value " + value);
              }
              value = getMutationTargetValue(target2);
              if (value != 2) {
                  throw new RuntimeException("target 2 has unexpected value " + value);
              }
          } catch (Exception ex) {
              ex.printStackTrace();
          }
      }
  
      private static void runMutator(Class c, int v) throws Exception {
          java.lang.reflect.Method m = c.getDeclaredMethod("mutate", int.class);
          m.invoke(null, v);
      }
  
      private static int getMutationTargetValue(Class c) throws Exception {
          java.lang.reflect.Field f = c.getDeclaredField("value");
          return f.getInt(null);
      }
  
      /**
       * See if we can load a class that isn't public to us.  We should be
       * able to load it but not instantiate it.
       */
      static void testAccess1(ClassLoader loader) {
          Class altClass;
  
          try {
              altClass = loader.loadClass("Inaccessible1");
          } catch (ClassNotFoundException cnfe) {
              System.err.println("loadClass failed");
              cnfe.printStackTrace();
              return;
          }
  
          /* instantiate */
          Object obj;
          try {
              obj = altClass.newInstance();
              System.err.println("ERROR: Inaccessible1 was accessible");
          } catch (InstantiationException ie) {
              System.err.println("newInstance failed: " + ie);
              return;
          } catch (IllegalAccessException iae) {
              System.out.println("Got expected access exception #1");
              //System.out.println("+++ " + iae);
              return;
          }
      }
  
      /**
       * See if we can load a class whose base class is not accessible to it
       * (though the base *is* accessible to us).
       */
      static void testAccess2(ClassLoader loader) {
          Class altClass;
  
          try {
              altClass = loader.loadClass("Inaccessible2");
              System.err.println("ERROR: Inaccessible2 was accessible: " + altClass);
          } catch (ClassNotFoundException cnfe) {
              Throwable cause = cnfe.getCause();
              if (cause instanceof IllegalAccessError) {
                  System.out.println("Got expected CNFE/IAE #2");
              } else {
                  System.err.println("Got unexpected CNFE/IAE #2");
                  cnfe.printStackTrace();
              }
          }
      }
  
      /**
       * See if we can load a class with an inaccessible interface.
       */
      static void testAccess3(ClassLoader loader) {
          Class altClass;
  
          try {
              altClass = loader.loadClass("Inaccessible3");
              System.err.println("ERROR: Inaccessible3 was accessible: " + altClass);
          } catch (ClassNotFoundException cnfe) {
              Throwable cause = cnfe.getCause();
              if (cause instanceof IllegalAccessError) {
                  System.out.println("Got expected CNFE/IAE #3");
              } else {
                  System.err.println("Got unexpected CNFE/IAE #3");
                  cnfe.printStackTrace();
              }
          }
      }
  
      /**
       * Test a doubled class that extends the base class.
       */
      static void testExtend(ClassLoader loader) {
          Class doubledExtendClass;
          Object obj;
  
          /* get the "alternate" version of DoubledExtend */
          try {
              doubledExtendClass = loader.loadClass("DoubledExtend");
              //System.out.println("+++ DoubledExtend is " + doubledExtendClass
              //    + " in " + doubledExtendClass.getClassLoader());
          } catch (ClassNotFoundException cnfe) {
              System.err.println("loadClass failed: " + cnfe);
              return;
          }
  
          /* instantiate */
          try {
              obj = doubledExtendClass.newInstance();
          } catch (InstantiationException ie) {
              System.err.println("newInstance failed: " + ie);
              return;
          } catch (IllegalAccessException iae) {
              System.err.println("newInstance failed: " + iae);
              return;
          } catch (LinkageError le) {
              System.out.println("Got expected LinkageError on DE");
              return;
          }
  
          /* use the base class reference to get a CL-specific instance */
          Base baseRef = (Base) obj;
          DoubledExtend de = baseRef.getExtended();
  
          /* try to call through it */
          try {
              String result;
  
              result = Base.doStuff(de);
              System.err.println("ERROR: did not get LinkageError on DE");
              System.err.println("(result=" + result + ")");
          } catch (LinkageError le) {
              System.out.println("Got expected LinkageError on DE");
              return;
          }
      }
  
      /**
       * Test a doubled class that extends the base class, but is okay since
       * it doesn't override the base class method.
       */
      static void testExtendOkay(ClassLoader loader) {
          Class doubledExtendOkayClass;
          Object obj;
  
          /* get the "alternate" version of DoubledExtendOkay */
          try {
              doubledExtendOkayClass = loader.loadClass("DoubledExtendOkay");
          } catch (ClassNotFoundException cnfe) {
              System.err.println("loadClass failed: " + cnfe);
              return;
          }
  
          /* instantiate */
          try {
              obj = doubledExtendOkayClass.newInstance();
          } catch (InstantiationException ie) {
              System.err.println("newInstance failed: " + ie);
              return;
          } catch (IllegalAccessException iae) {
              System.err.println("newInstance failed: " + iae);
              return;
          } catch (LinkageError le) {
              System.err.println("Got unexpected LinkageError on DEO");
              le.printStackTrace();
              return;
          }
  
          /* use the base class reference to get a CL-specific instance */
          BaseOkay baseRef = (BaseOkay) obj;
          DoubledExtendOkay de = baseRef.getExtended();
  
          /* try to call through it */
          try {
              String result;
  
              result = BaseOkay.doStuff(de);
              System.out.println("Got DEO result " + result);
          } catch (LinkageError le) {
              System.err.println("Got unexpected LinkageError on DEO");
              le.printStackTrace();
              return;
          }
      }
  
      /**
       * Try to access a doubled class through a class that implements
       * an interface declared in a different class.
       */
      static void testInterface(ClassLoader loader) {
          Class getDoubledClass;
          Object obj;
  
          /* get GetDoubled from the "alternate" class loader */
          try {
              getDoubledClass = loader.loadClass("GetDoubled");
          } catch (ClassNotFoundException cnfe) {
              System.err.println("loadClass failed: " + cnfe);
              return;
          }
  
          /* instantiate */
          try {
              obj = getDoubledClass.newInstance();
          } catch (InstantiationException ie) {
              System.err.println("newInstance failed: " + ie);
              return;
          } catch (IllegalAccessException iae) {
              System.err.println("newInstance failed: " + iae);
              return;
          } catch (LinkageError le) {
              // Dalvik bails here
              System.out.println("Got LinkageError on GD");
              return;
          }
  
          /*
           * Cast the object to the interface, and try to use it.
           */
          IGetDoubled iface = (IGetDoubled) obj;
          try {
              /* "de" will be the wrong variety of DoubledExtendOkay */
              DoubledExtendOkay de = iface.getDoubled();
              // reference impl bails here
              String str = de.getStr();
          } catch (LinkageError le) {
              System.out.println("Got LinkageError on GD");
              return;
          }
          System.err.println("Should have failed by now on GetDoubled");
      }
  
      /**
       * Throw an abstract class into the middle and see what happens.
       */
      static void testAbstract(ClassLoader loader) {
          Class abstractGetClass;
          Object obj;
  
          /* get AbstractGet from the "alternate" loader */
          try {
              abstractGetClass = loader.loadClass("AbstractGet");
          } catch (ClassNotFoundException cnfe) {
              System.err.println("loadClass ta failed: " + cnfe);
              return;
          }
  
          /* instantiate */
          try {
              obj = abstractGetClass.newInstance();
          } catch (InstantiationException ie) {
              System.err.println("newInstance failed: " + ie);
              return;
          } catch (IllegalAccessException iae) {
              System.err.println("newInstance failed: " + iae);
              return;
          } catch (LinkageError le) {
              System.out.println("Got LinkageError on TA");
              return;
          }
  
          /* use the base class reference to get a CL-specific instance */
          BaseOkay baseRef = (BaseOkay) obj;
          DoubledExtendOkay de = baseRef.getExtended();
  
          /* try to call through it */
          try {
              String result;
  
              result = BaseOkay.doStuff(de);
          } catch (LinkageError le) {
              System.out.println("Got LinkageError on TA");
              return;
          }
          System.err.println("Should have failed by now in testAbstract");
      }
  
      /**
       * Test a doubled class that implements a common interface.
       */
      static void testImplement(ClassLoader loader) {
          Class doubledImplementClass;
          Object obj;
  
          useImplement(new DoubledImplement(), true);
  
          /* get the "alternate" version of DoubledImplement */
          try {
              doubledImplementClass = loader.loadClass("DoubledImplement");
          } catch (ClassNotFoundException cnfe) {
              System.err.println("loadClass failed: " + cnfe);
              return;
          }
  
          /* instantiate */
          try {
              obj = doubledImplementClass.newInstance();
          } catch (InstantiationException ie) {
              System.err.println("newInstance failed: " + ie);
              return;
          } catch (IllegalAccessException iae) {
              System.err.println("newInstance failed: " + iae);
              return;
          } catch (LinkageError le) {
              System.out.println("Got LinkageError on DI (early)");
              return;
          }
  
          /* if we lived this long, try to do something with it */
          ICommon icommon = (ICommon) obj;
          useImplement(icommon.getDoubledInstance(), false);
      }
  
      /**
       * Do something with a DoubledImplement instance.
       */
      static void useImplement(DoubledImplement di, boolean isOne) {
          //System.out.println("useObject: " + di.toString() + " -- "
          //    + di.getClass().getClassLoader());
          try {
              di.one();
              if (!isOne) {
                  System.err.println("ERROR: did not get LinkageError on DI");
              }
          } catch (LinkageError le) {
              if (!isOne) {
                  System.out.println("Got LinkageError on DI (late)");
              } else {
                  throw le;
              }
          }
      }
  
  
      /**
       * Test a class that implements an interface with a super-interface
       * that refers to a doubled class.
       */
      static void testIfaceImplement(ClassLoader loader) {
          Class ifaceImplClass;
          Object obj;
  
          /*
           * Create an instance of IfaceImpl.  We also pull in
           * DoubledImplement2 from the other class loader; without this
           * we don't fail in some implementations.
           */
          try {
              ifaceImplClass = loader.loadClass("IfaceImpl");
              ifaceImplClass = loader.loadClass("DoubledImplement2");
          } catch (ClassNotFoundException cnfe) {
              System.err.println("loadClass failed: " + cnfe);
              return;
          }
  
          /* instantiate */
          try {
              obj = ifaceImplClass.newInstance();
          } catch (InstantiationException ie) {
              System.err.println("newInstance failed: " + ie);
              return;
          } catch (IllegalAccessException iae) {
              System.err.println("newInstance failed: " + iae);
              return;
          } catch (LinkageError le) {
              System.out.println("Got LinkageError on IDI (early)");
              //System.out.println(le);
              return;
          }
  
          /*
           * Without the pre-load of FancyLoader->DoubledImplement2, some
           * implementations will happily execute through this part.  "obj"
           * comes from FancyLoader, but the di2 returned from ifaceSuper
           * comes from the application class loader.
           */
          IfaceSuper ifaceSuper = (IfaceSuper) obj;
          DoubledImplement2 di2 = ifaceSuper.getDoubledInstance2();
          di2.one();
      }
  
      static void testClassForName() throws Exception {
          System.out.println(Class.forName("Main").toString());
          try {
              System.out.println(Class.forName("Main", false, null).toString());
          } catch (ClassNotFoundException expected) {
              System.out.println("Got expected ClassNotFoundException");
          }
      }
  }
  