//
// 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
//////////////////////////////////////////////////////////////////////////
**
** Find all jars in system classpath
**
static File[] findSysClassPathFiles()
{
entries := 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
Env.cur.vars.get("sun.boot.class.path", "").split(File.pathSep[0]).each |Str path|
{
f := File.os(path)
if (!f.exists) return
if (!f.isDir && f.ext != "jar") return
if (javaIgnore[f.name] != null) return
entries.add(f)
}
// {java}lib/rt.jar (only if sun.boot.class.path failed)
lib := File.os(Env.cur.vars.get("java.home", "") + File.sep + "lib")
if (entries.isEmpty)
{
rt := lib + `rt.jar`
if (rt.exists) entries.add(rt)
}
// {java}lib/ext
lib.plus(`ext/`).list.each |f|
{
if (f.ext != "jar") return
if (javaIgnore[f.name] != null) return
entries.add(f)
}
// {fan}lib/java/ext
// {fan}lib/java/ext/{plat}
addJars(entries, Env.cur.homeDir + `lib/java/ext/`)
addJars(entries, Env.cur.homeDir + `lib/java/ext/${Env.cur.platform}/`)
// -classpath
Env.cur.vars.get("java.class.path", "").split(File.pathSep[0]).each |Str path|
{
f := File.os(path)
if (f.exists) entries.add(f)
}
return entries
}
private static Void addJars(File[] entries, File dir)
{
dir.list.each |f| { if (f.ext == "jar") entries.add(f) }
}
// ignore the common big jars that ship with
// HotSpot which don't contain public java packages
private static const Str:Str javaIgnore := [:].addList(
[
"deploy.jar",
"charsets.jar",
"javaws.jar",
"jsse.jar",
"resources.jar",
"dnsns.jar",
"localedata.jar",
"sunec.jar",
"sunec_provider.jar",
"sunjce_provider.jar",
"sunmscapi.jar",
"sunpkcs11.jar",
"zipfs.jar",
])
//////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////
**
** Construct for given list of jar files or directoris.
**
new make(File[] files)
{
start := Duration.now
this.files = files
this.packages = loadPackages
this.dur = Duration.now - start
}
//////////////////////////////////////////////////////////////////////////
// Access
//////////////////////////////////////////////////////////////////////////
** Class path files (jar or dirs) to search
const File[] files
** Open zip files
private Zip[] zips := [,]
** Packages keyed by package name in "." format
Str:ClassPathPackage packages { private set }
** Return list of files.
override Str toStr() { files.toStr }
** Close all open zip files
Void close()
{
zips.each |zip| { zip.close }
}
** Load time duration
private Duration dur
//////////////////////////////////////////////////////////////////////////
// Loading
//////////////////////////////////////////////////////////////////////////
private Str:ClassPathPackage loadPackages()
{
acc := Str:ClassPathPackage[:]
files.each |File f| { loadFile(acc, f) }
return acc
}
private Void loadFile(Str:ClassPathPackage acc, File f)
{
if (f.isDir)
{
f.walk |File x| { accept(acc, x.uri.relTo(f.uri), f, false) }
}
else
{
Zip? zip := null
try
{
zip = Zip.open(f)
isBoot := f.name == "rt.jar"
zips.add(zip)
zip.contents.each |File x, Uri uri| { accept(acc, uri, x, isBoot) }
}
catch (Err e)
{
echo("ERROR: $typeof: $f")
e.trace
}
}
}
private Void accept(Str:ClassPathPackage acc, Uri uri, File file, Bool isBoot)
{
// don't care about anything but .class files
if (uri.ext != "class") return
// convert URI to package name, skip non-public 'com.sun' if rt.jar
packageName := uri.path[0..-2].join(".")
if (isBoot)
{
if (packageName.startsWith("com.sun") || packageName.startsWith("sun"))
return
}
// get simple name of class
name := uri.basename
if (name == "Void") return
// get or add package
package := acc[packageName]
if (package == null) acc[packageName] = package = ClassPathPackage(packageName)
// add class to package if not already defined
if (package.classes[name] == null) package.classes[name] = file
}
Void dump(OutStream out := Env.cur.out)
{
out.printLine("--- ClassPath ---")
out.printLine("Packages Found:")
classes := 0
packages.vals.sort.each |p|
{
classes += p.classes.size
out.printLine(" $p [" + p.classes.size + "]")
}
out.printLine("ClassPath Files:")
files.each |File f| { echo(" $f") }
out.printLine("${dur.toLocale}, $files.size files, $packages.size packages, $classes classes")
out.printLine("-----------------")
}
static Void main()
{
cp := ClassPath(findSysClassPathFiles)
cp.close
cp.dump
}
}
**************************************************************************
** ClassPathPackage
**************************************************************************
**
** ClassPathPackage models a single package found in the class
** path with a map of classnames to classfiles.
**
class ClassPathPackage
{
new make(Str name) { this.name = name }
** Package name in "." format
const Str name
** Classfiles keyed by simple name (not qualified name)
Str:File classes := [:] { private set }
** Return name
override Str toStr() { name }
}