Fan

 

class

compilerJava::ClassPath

sys::Obj
  compilerJava::ClassPath
//
// Copyright (c) 2008, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//    15 Nov 08  Brian Frank  Creation
//

**
** ClassPath models a Java classpath to resolve package
** names to types.  Since the standard Java APIs don't expose
** this, we have go thru a lot of pain.
**
class ClassPath
{

//////////////////////////////////////////////////////////////////////////
// Construction
//////////////////////////////////////////////////////////////////////////

  **
  ** Attempt to derive the current classpath by looking at
  ** system properties.
  **
  static ClassPath makeForCurrent()
  {
    jars := File[,]

    // System.property "sun.boot.class.path"; this is preferable
    // to trying to figure out rt.jar - on platforms like Mac OS X
    // the classes are in very non-standard locations
    Sys.env.get("sun.boot.class.path", "").split(File.pathSep[0]).each |Str path|
    {
      f := File.os(path)
      // skip big jar files we can probably safely ignore
      if (f.ext != "jar") return
      if (f.name == "deploy.jar") return
      if (f.name == "charsets.jar") return
      if (f.name == "javaws.jar") return
      jars.add(f)
    }

    // {java}lib/rt.jar (only if sun.boot.class.path failed)
    lib := File.os(Sys.env.get("java.home", "") + File.sep + "lib")
    if (jars.isEmpty)
    {
      rt := lib + `rt.jar`
      if (rt.exists) jars.add(rt)
    }

    // {java}lib/ext
    ext := lib + `ext/`
    ext.list.each |File extJar| { if (extJar.ext == "jar") jars.add(extJar) }


    // -classpath
    Sys.env.get("java.class.path", "").split(File.pathSep[0]).each |Str path|
    {
      f := File.os(path)
      if (f.exists) jars.add(f)
    }

    return make(jars)
  }

  **
  ** Make for current set of jars.
  **
  new make(File[] jars)
  {
    this.jars = jars
    this.classes = loadClasses
  }

//////////////////////////////////////////////////////////////////////////
// State
//////////////////////////////////////////////////////////////////////////

  **
  ** Jar files to search
  **
  const File[] jars

  **
  ** List of classes keyed by package name in class path
  **
  const Str:Str[] classes

  **
  ** Return list of jar files.
  **
  override Str toStr()
  {
    return jars.toStr
  }

//////////////////////////////////////////////////////////////////////////
// Loading
//////////////////////////////////////////////////////////////////////////

  **
  ** Load the map of package:class[] by walking every jar file
  **
  private Str:Str[] loadClasses()
  {
    acc := Str:Str[][:]
    jars.each |File f|  { loadJar(acc, f) }
    return acc
  }

  **
  ** Load the map of package:class[] by walking entries in jar file
  **
  private Void loadJar(Str:Str[] acc, File f)
  {
    Zip? zip := null
    try
    {
      zip = Zip.open(f)
      zip.contents.each |File x, Uri uri|
      {
        if (uri.ext != "class") return
        package := uri.path[0..-2].join(".")
        if (package.startsWith("com.sun") || package.startsWith("sun")) return
        name := uri.basename
        if (name == "Void") return
        classes := acc[package]
        if (classes == null) acc[package] = classes = Str[,]
        if (!classes.contains(name)) classes.add(name)
      }
    }
    catch {}
    finally { if (zip != null) zip.close }
  }

  static Void main()
  {
    t1 := Duration.now
    cp := makeForCurrent
    t2:= Duration.now
    echo("ClassPath.makeForCurrent: ${(t2-t1).toMillis}ms")

    echo("Jars Found:")
    cp.jars.each |File f| { echo("  $f") }

    echo("Packages Found:")
    cp.classes.keys.sort.each |Str p| { echo("  $p [" + cp.classes[p].size + "]") }
  }

}