/*
   * @implementation
   * If you modify this method, you should also modify the related method
   * StandaloneJarBuilder.getStartPointInstanceJavaExpression
   */
  RTValue getStartPointInstance(
      String className, MachineFunction mf, RTExecutionContext executionContext)
      throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
          InvocationTargetException, NoSuchFieldException {

    if (mf == null) {
      throw new NullPointerException(
          "Invalid MachineFunction in CALClassLoader.getStartPointInstance() for "
              + className
              + ".");
    }
    Class<?> c = loadClass(className);

    if (mf.isDataConstructor()) {
      // This is a data constructor.
      // Get the static 'make' method.
      Method m = c.getMethod("make", new Class[] {});
      return (RTValue) m.invoke(null, new Object[] {});
    }

    FunctionGroupInfo fgi = module.getFunctionGroupInfo(mf);
    if (fgi == null) {
      throw new NullPointerException(
          "Invalid FunctionGroupInfo in CALClassLoader.getStartPointInstance() for "
              + mf.getQualifiedName()
              + ".");
    }

    if (mf.getArity() == 0) {
      // Get the static 'make' method.
      if (fgi.getNCAFs() + fgi.getNZeroArityFunctions() <= 1) {
        Method m = c.getMethod("make", new Class[] {RTExecutionContext.class});
        return (RTValue) m.invoke(null, new Object[] {executionContext});
      } else {
        Method m = c.getMethod("make", new Class[] {int.class, RTExecutionContext.class});
        int functionIndex = fgi.getFunctionIndex(mf.getName());
        return (RTValue)
            m.invoke(null, new Object[] {Integer.valueOf(functionIndex), executionContext});
      }
    }

    // Access the static instance field.
    String instanceFieldName = CALToJavaNames.getInstanceFieldName(mf.getQualifiedName(), module);
    Field field = c.getField(instanceFieldName);

    return (RTValue) field.get(null);
  }
  /**
   * {@inheritDoc} This classloader implementation calls findClass() on the current instance, before
   * calling loadClass() on the parent.
   */
  @Override
  protected Class<?> findClass(String name) throws ClassNotFoundException {

    final long beforeFindClass;
    if (PERFORM_TIMING) {
      beforeFindClass = System.currentTimeMillis();
    } else {
      beforeFindClass = 0;
    }

    if (DEBUG_OUTPUT) {
      if (adjunctLoader) {
        System.out.println(
            "adjunct loader for "
                + getModuleName()
                + ": findClass - "
                + name
                + " => thread = "
                + getCurrentThreadName());
      } else {
        System.out.println(
            "loader for "
                + getModuleName()
                + ": findClass - "
                + name
                + " => thread = "
                + getCurrentThreadName());
      }
    }

    // The adjunct loader should only return the class if it's in the set of adjunct classes.
    if (adjunctLoader
        && !adjunctClasses.contains(name)
        && (name.indexOf('$') < 0
            || !adjunctClasses.contains(name.substring(0, name.indexOf('$'))))) {
      throw new ClassNotFoundException();
    }

    // Get the module package component of the request class.
    ModuleName classModuleName = CALToJavaNames.getModuleNameFromPackageName(name);
    if (classModuleName == null) {
      throw new ClassNotFoundException();
    }

    // Check if we handle this package.
    if (classModuleName.equals(getModuleName())) {

      byte[] data = getBytecodeForClassInternal(name);

      // Instantiate the class.
      if (data == null) {
        // TODOEL: perhaps this should throw a NoClassDefFoundError.
        //  This would only make sense if we can ensure that this classloader can only be asked for
        // a class by our code --
        //  it wouldn't do for a client to encounter a LinkageError if it were to ask the
        // classloader for such a class.
        throw new ClassNotFoundException("Unable to find class: " + name);
      }
      // Increment the number of classes and bytes loaded

      nClassesLoaded++;
      nBytesLoaded += data.length;

      Class<?> c = defineClass(name, data, 0, data.length);

      // Add to the list of loaded classes.  This is used when resetting cached CAF results.
      loadedClasses.add(c);

      if (PERFORM_TIMING) {
        long afterFindClass = System.currentTimeMillis();
        findClassTimeMS += (afterFindClass - beforeFindClass);
      }

      if (DUMP_LOADED_CLASS_NAMES) {
        System.out.println(name);
        // System.out.println(name + " " + data.length);
      }

      if (DEBUG_OUTPUT) {
        System.out.println("    Found");
      }
      return c;
    }

    // Classes in other modules will always be loaded by the module loader, never by the adjunct
    // loader, so we only need to check for the class being in another module if this loader
    // is not an adjunct loader.
    if (!adjunctLoader) {
      CALClassLoader moduleLoader = dependeeModuleLoaders.get(classModuleName);
      if (moduleLoader != null) {
        return moduleLoader.loadClass(name);
      }
    }
    throw new ClassNotFoundException(
        "Unable to find class: " + name + " with CALClassLoader for module " + getModuleName());
  }