/**
   * Constructor. The first time an RJavaClassLoader is created, it is cached as the primary loader.
   *
   * @param path path of the rJava package
   * @param libpath lib sub directory of the rJava package
   */
  public RJavaClassLoader(String path, String libpath) {
    super(new URL[] {});
    // respect rJava.debug level
    String rjd = System.getProperty("rJava.debug");
    if (rjd != null && rjd.length() > 0 && !rjd.equals("0")) verbose = true;
    if (verbose) System.out.println("RJavaClassLoader(\"" + path + "\",\"" + libpath + "\")");
    if (primaryLoader == null) {
      primaryLoader = this;
      if (verbose) System.out.println(" - primary loader");
    } else {
      if (verbose)
        System.out.println(" - NOT primrary (this=" + this + ", primary=" + primaryLoader + ")");
    }
    libMap = new HashMap /*<String,UnixFile>*/();

    classPath = new Vector /*<UnixFile>*/();
    classPath.add(new UnixDirectory(path + "/java"));

    rJavaPath = path;
    rJavaLibPath = libpath;

    /* load the rJava library */
    UnixFile so = new UnixFile(rJavaLibPath + "/rJava.so");
    if (!so.exists()) so = new UnixFile(rJavaLibPath + "/rJava.dll");
    if (so.exists()) libMap.put("rJava", so);

    /* load the jri library */
    UnixFile jri = new UnixFile(path + "/jri/libjri.so");
    String rarch = System.getProperty("r.arch");
    if (rarch != null && rarch.length() > 0) {
      UnixFile af = new UnixFile(path + "/jri" + rarch + "/libjri.so");
      if (af.exists()) jri = af;
      else {
        af = new UnixFile(path + "/jri" + rarch + "/jri.dll");
        if (af.exists()) jri = af;
      }
    }
    if (!jri.exists()) jri = new UnixFile(path + "/jri/libjri.jnilib");
    if (!jri.exists()) jri = new UnixFile(path + "/jri/jri.dll");
    if (jri.exists()) {
      libMap.put("jri", jri);
      if (verbose) System.out.println(" - registered JRI: " + jri);
    }

    /* if we are the primary loader, make us the context loader so
    projects that rely on the context loader pick us */
    if (primaryLoader == this) Thread.currentThread().setContextClassLoader(this);

    if (verbose) {
      System.out.println("RJavaClassLoader initialized.\n\nRegistered libraries:");
      for (Iterator entries = libMap.keySet().iterator(); entries.hasNext(); ) {
        Object key = entries.next();
        System.out.println("  " + key + ": '" + libMap.get(key) + "'");
      }
      System.out.println("\nRegistered class paths:");
      for (Enumeration e = classPath.elements(); e.hasMoreElements(); )
        System.out.println("  '" + e.nextElement() + "'");
      System.out.println("\n-- end of class loader report --");
    }
  }
  // {{{ findLibrary
  protected String findLibrary(String name) {
    if (verbose) System.out.println("RJavaClassLoader.findLibrary(\"" + name + "\")");
    // if (name.equals("rJava"))
    //    return rJavaLibPath+"/"+name+".so";

    UnixFile u = (UnixFile) libMap.get(name);
    String s = null;
    if (u != null && u.exists()) s = u.getPath();
    if (verbose) System.out.println(" - mapping to " + ((s == null) ? "<none>" : s));

    return s;
  }
  // {{{ findResource
  public URL findResource(String name) {
    if (verbose) System.out.println("RJavaClassLoader: findResource('" + name + "')");

    // {{{ use the standard way
    if (useSystem) {
      try {
        URL u = super.findResource(name);
        if (u != null) {
          if (verbose)
            System.out.println("RJavaClassLoader: found resource in " + u + " using URL loader.");
          return u;
        }
      } catch (Exception fre) {
      }
    }
    // }}}

    // {{{ iterate through the classpath
    if (verbose) System.out.println(" - resource not found with URL loader, trying alternative");
    Enumeration /*<UnixFile>*/ e = classPath.elements();
    while (e.hasMoreElements()) {
      UnixFile cp = (UnixFile) e.nextElement();

      try {
        /* is a file - assume it is a jar file */
        if (cp instanceof UnixJarFile) {
          URL u = ((UnixJarFile) cp).getResource(name);
          if (u != null) {
            if (verbose) System.out.println(" - found in a JAR file, URL " + u);
            return u;
          }
        } else if (cp instanceof UnixDirectory) {
          UnixFile res_f = new UnixFile(cp.getPath() + "/" + name);
          if (res_f.isFile()) {
            if (verbose) System.out.println(" - find as a file: " + res_f);
            return res_f.toURL();
          }
        }
      } catch (Exception iox) {
      }
    }
    // }}}
    return null;
  }
    /* @Override */
    public void update() {
      try {
        if (zfile != null) {
          zfile.close();
        }
        zfile = new ZipFile(this);
      } catch (Exception tryCloseX) {
      }

      /* time stamp */
      super.update();
    }
  /** adds an entry to the class path */
  public void addClassPath(String cp) {
    UnixFile f = new UnixFile(cp);

    // use the URLClassLoader
    if (useSystem) {
      try {
        addURL(f.toURL());
        if (verbose)
          System.out.println("RJavaClassLoader: added '" + cp + "' to the URL class path loader");
        // return; // we need to add it anyway so it appears in .jclassPath()
      } catch (Exception ufe) {
      }
    }

    UnixFile g = null;
    if (f.isFile() && (f.getName().endsWith(".jar") || f.getName().endsWith(".JAR"))) {
      g = new UnixJarFile(cp);
      if (verbose)
        System.out.println(
            "RJavaClassLoader: adding Java archive file '" + cp + "' to the internal class path");
    } else if (f.isDirectory()) {
      g = new UnixDirectory(cp);
      if (verbose)
        System.out.println(
            "RJavaClassLoader: adding class directory '" + cp + "' to the internal class path");
    } else if (verbose)
      System.err.println(
          f.exists()
              ? ("WARNING: the path '"
                  + cp
                  + "' is neither a directory nor a .jar file, it will NOT be added to the internal class path!")
              : ("WARNING: the path '"
                  + cp
                  + "' does NOT exist, it will NOT be added to the internal class path!"));

    if (g != null && !classPath.contains(g)) {
      // this is the real meat - add it to our internal list
      classPath.add(g);
      // this is just cosmetics - it doesn't really have any meaning
      System.setProperty(
          "java.class.path",
          System.getProperty("java.class.path") + File.pathSeparator + g.getPath());
    }
  }
  // {{{ findClass
  protected Class findClass(String name) throws ClassNotFoundException {
    Class cl = null;
    if (verbose) System.out.println("" + this + ".findClass(" + name + ")");
    if ("RJavaClassLoader".equals(name)) return getClass();

    // {{{ use the usual method of URLClassLoader
    if (useSystem) {
      try {
        cl = super.findClass(name);
        if (cl != null) {
          if (verbose)
            System.out.println("RJavaClassLoader: found class " + name + " using URL loader");
          return cl;
        }
      } catch (Exception fnf) {
        if (verbose) System.out.println(" - URL loader did not find it: " + fnf);
      }
    }
    if (verbose) System.out.println("RJavaClassLoader.findClass(\"" + name + "\")");
    // }}}

    // {{{ iterate through the elements of the class path
    InputStream ins = null;
    Exception defineException = null;
    Enumeration /*<UnixFile>*/ e = classPath.elements();
    while (e.hasMoreElements()) {
      UnixFile cp = (UnixFile) e.nextElement();

      if (verbose) System.out.println(" - trying class path \"" + cp + "\"");
      try {
        ins = null;
        /* a file - assume it is a jar file */
        if (cp instanceof UnixJarFile) {
          ins = ((UnixJarFile) cp).getResourceAsStream(classNameToFile(name) + ".class");
          if (verbose)
            System.out.println(
                "   JAR file, can get '"
                    + classNameToFile(name)
                    + "'? "
                    + ((ins == null) ? "NO" : "YES"));
        } else if (cp instanceof UnixDirectory) {
          UnixFile class_f = new UnixFile(cp.getPath() + "/" + classNameToFile(name) + ".class");
          if (class_f.isFile()) {
            ins = new FileInputStream(class_f);
          }
          if (verbose)
            System.out.println(
                "   Directory, can get '" + class_f + "'? " + ((ins == null) ? "NO" : "YES"));
        }

        /* some comments on the following :

           we could call ZipEntry.getSize in case of a jar file to
           find out the size of the byte[] directly

           also ByteBuffer seems more efficient, but the ByteBuffer class
           is java >= 1.4 and the defineClass method that uses the class
           is java >= 1.5
        */

        if (ins != null) {
          int al = 128 * 1024;
          byte fc[] = new byte[al];
          int n = ins.read(fc);
          int rp = n;
          if (verbose) System.out.println("  loading class file, initial n = " + n);
          while (n > 0) {
            if (rp == al) {
              int nexa = al * 2;
              if (nexa < 512 * 1024) nexa = 512 * 1024;
              byte la[] = new byte[nexa];
              System.arraycopy(fc, 0, la, 0, al);
              fc = la;
              al = nexa;
            }
            n = ins.read(fc, rp, fc.length - rp);
            if (verbose) System.out.println("  next n = " + n + " (rp=" + rp + ", al=" + al + ")");
            if (n > 0) rp += n;
          }
          ins.close();
          n = rp;
          if (verbose)
            System.out.println("RJavaClassLoader: loaded class " + name + ", " + n + " bytes");
          try {
            cl = defineClass(name, fc, 0, n);
          } catch (Exception dce) {
            // we want to save this one so we can pass it on
            defineException = dce;
            break;
          }
          if (verbose) System.out.println("  defineClass('" + name + "') returned " + cl);
          // System.out.println(" - class = "+cl);
          return cl;
        }
      } catch (Exception ex) {
        // System.out.println(" * won't work: "+ex.getMessage());
      }
    }
    // }}}

    if (defineException != null) // we bailed out on class interpretation, re-throw it
    throw (new ClassNotFoundException(
          "Class not found - candidate class binary found but could not be loaded",
          defineException));

    // giving up
    if (verbose) System.out.println("    >> ClassNotFoundException ");
    if (cl == null) {
      throw (new ClassNotFoundException());
    }
    return cl;
  }