/*
* @(#)AppletClassLoader.java 1.18 95/09/18
*
* Copyright (c) 1994 Sun Microsystems, Inc. All Rights Reserved.
*
* Permission to use, copy, modify, and distribute this software
* and its documentation for NON-COMMERCIAL purposes and without
* fee is hereby granted provided that this copyright notice
* appears in all copies. Please refer to the file "copyright.html"
* for further important copyright and licensing information.
*
* SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
* THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
* ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
* DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
*/
package netscape.applet;
import java.util.Hashtable;
import java.io.InputStream;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.net.URL;
import java.net.URLConnection;
import java.net.MalformedURLException;
import java.util.Vector;
/**
* This class defines an applet class loader.
*
* @version 1.18, 18 Sep 1995
* @author Arthur van Hoff
*/
final class AppletClassLoader extends ClassLoader {
Hashtable classes;
URL codeBaseURL;
URL archiveURL;
MozillaAppletContext context;
int nativeZipFile;
static boolean wantTiming = false; // change this to get zip timing info
long loaderTime;
boolean mayScript;
// list of all AppletClassLoaders which are active and available for
// sharing.
static Vector classloaders = new Vector(10);
// number of EmbeddedAppletFrames using this AppletClassLoader
// if 0, there is only one and the classloader will not be shared.
int refCount;
/**
* Using this to allocate an AppletClassLoader instead of the
* constructor will re-use classloaders for the same codebase
*/
static synchronized AppletClassLoader getClassLoader(
MozillaAppletContext cx,
URL codebase, URL archive, boolean mayScript) {
AppletClassLoader loader = null;
// search the available classloaders
for (int i = 0; i < classloaders.size(); i++) {
AppletClassLoader test
= (AppletClassLoader)classloaders.elementAt(i);
/* classloaders are shared iff all of these are true:
* - the codebase urls match
* - the archive urls match
* - the mayscript permissions match
* - either there is no mayscript, or the contexts match
* (i.e. the applets are on the same page)
*/
if (codebase.equals(test.codeBaseURL) &&
archive.equals(test.archiveURL) &&
mayScript == test.mayScript &&
(!mayScript || cx == test.context)) {
loader = test;
break;
}
}
if (loader == null) {
loader = new AppletClassLoader(cx, codebase, archive);
loader.mayScript = mayScript;
classloaders.addElement(loader);
}
loader.refCount++;
return loader;
}
/**
* Release the classloader.
* This removes it from the global classloader table if the
* refcount goes to zero.
*/
void releaseClassLoader() {
if (refCount == 0) {
throw new InternalError("tried to release unshared classloader");
}
if (MozillaAppletContext.debug > 1) {
System.err.println("# released reference to: " + this);
}
if (--refCount == 0) {
classloaders.removeElement(this);
if (MozillaAppletContext.debug > 1) {
System.err.println("# removed: " + this);
}
}
}
/**
* Load applets from the codebase URL. Remember the document URL
* so that the AppletSecurity manager can allow access back to
* the documentURL.
*/
AppletClassLoader(MozillaAppletContext cx, URL codebase, URL archive) {
refCount = 0;
// false is the default - i didn't change the signature of this
// function so i could avoid disturbing the native code in
// mo_java.c that allocates one of these.
mayScript = false;
// try downloading a zip file
String file = archive.getFile();
if (file.endsWith(".zip")) {
try {
if (wantTiming) {
long before = System.currentTimeMillis();
nativeZipFile = openZipFile(archive.openStream());
long after = System.currentTimeMillis();
long duration = after - before;
loaderTime += duration;
System.err.println("# Zip download time: " + archive + ": "
+ duration + " (total = " + loaderTime + ")");
}
else {
nativeZipFile = openZipFile(archive.openStream());
}
if (context != null && context.debug > 1) {
System.err.println("# Loading classes from downloaded zip file: " + archive);
}
} catch (IOException e) {
System.err.println("# Failed to pull over zip file " + archive);
}
}
// set up the codebase:
// Strip the tail end of the URL so that it ends in a /
file = codebase.getFile();
int i = file.lastIndexOf('/');
if ((i > 0) && (i < file.length()-1)) {
try {
codebase = new URL(codebase, file.substring(0, i+1));
} catch (MalformedURLException e) {
}
}
this.codeBaseURL = codebase;
this.archiveURL = archive;
this.context = cx;
this.classes = new Hashtable();
}
native int openZipFile(InputStream in) throws IOException;
native byte[] loadFromZipFile(int zipFile, String path) throws IOException;
native void closeZipFile(int zipFile) throws IOException;
void close() {
if (nativeZipFile != 0) {
try {
closeZipFile(nativeZipFile);
if (context != null && context.debug > 1) {
System.err.println("# Closed downloaded zip file: " + codeBaseURL);
}
} catch (IOException e) {
if (context != null && context.debug > 1) {
System.err.println("# Failed to close downloaded zip file: " + codeBaseURL);
}
}
nativeZipFile = 0;
}
}
protected void finalize() {
close();
if (MozillaAppletContext.debug > 1) {
System.err.println("# finalized: " + this);
}
}
/**
* Check if this classloader has JavaScript privileges.
*/
boolean mayScript() {
return mayScript;
}
/**
* Load a class from a URL. This does the actual work of loading the
* class and then defining it.
*/
private Class loadClass(String name, URL url) throws IOException {
byte[] data = readURL(url);
return defineClass(name, data, 0, data.length);
}
/**
* Load a class from this class loader. This method is used by applets
* that want to explicitly load a class.
*/
public Class loadClass(String name) throws ClassNotFoundException {
return loadClass(name, true);
}
/**
* Load and resolve a class. This method is called by the java runtime
* to get a class that another class needs (e.g. a superclass).
*/
protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class clazz;
if (wantTiming) {
long before = System.currentTimeMillis();
clazz = loadClass1(name, resolve);
long after = System.currentTimeMillis();
long duration = after - before;
loaderTime += duration;
System.err.println("# Class load time: " + name + ": "
+ duration + " (total = " + loaderTime + ")");
}
else
clazz = loadClass1(name, resolve);
return clazz;
}
Class loadClass1(String name, boolean resolve) throws ClassNotFoundException {
Class cl = (Class)classes.get(name);
if (cl == null) {
// XXX: We should call a Security.checksPackageAccess() native method, and pass name as arg
SecurityManager security = System.getSecurityManager();
if (security != null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
security.checkPackageAccess(name.substring(0, i));
}
}
try {
return findSystemClass(name);
} catch (ClassNotFoundException e) {
}
cl = findClass(name);
}
if (cl == null) {
throw new ClassNotFoundException(name);
}
if (this.nativeZipFile != 0) {
SecurityManager.checksURLConnect(this.archiveURL);
}
if (resolve) {
resolveClass(cl);
}
return cl;
}
/**
* This method finds a class. The returned class
* may be unresolved. This method has to be synchronized
* to avoid two threads loading the same class at the same time.
* Must be called with the actual class name.
*/
private synchronized Class findClass(String name) throws ClassNotFoundException {
Class cl = (Class)classes.get(name);
if (cl != null) {
return cl;
}
if (context != null && context.debug > 1) {
System.err.println("# Find class " + name);
}
// XXX: We should call a Security.checksPackageDefinition() native method, and pass name as arg
SecurityManager security = System.getSecurityManager();
if (security != null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
security.checkPackageDefinition(name.substring(0, i));
}
}
String cname = name.replace('.', '/') + ".class";
if (nativeZipFile != 0) {
try {
byte[] data = loadFromZipFile(nativeZipFile, cname);
if (data != null) {
cl = defineClass(name, data, 0, data.length);
if (cl != null && context != null && context.debug > 1) {
System.err.println("# Loaded " + cname + " from downloaded zip file.");
}
}
} catch (IOException e) {
if (context.debug > 1)
System.err.println("# Failed to load " + cname + " from downloaded zip file.");
}
}
if (cl == null) {
URL url;
try {
url = new URL(codeBaseURL, cname);
} catch (MalformedURLException e) {
throw new ClassNotFoundException(name);
}
if (context != null && context.debug > 1) {
System.err.println("# Fetching " + url);
}
try {
cl = loadClass(name, url);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
if (!name.equals(cl.getName())) {
Class oldcl = cl;
cl = null;
throw new ClassFormatError(name + " != " + oldcl.getName());
}
classes.put(name, cl);
return cl;
}
byte[] getResource(URL url) {
byte[] data = null;
String urlFile = url.getFile();
String codebaseFile = codeBaseURL.getFile();
System.out.println("url="+url+" codebase="+codeBaseURL);
if (!urlFile.startsWith(codebaseFile))
return null;
String resourcePath = urlFile.substring(codebaseFile.length());
System.out.println("resourcePath="+resourcePath);
try {
data = loadFromZipFile(nativeZipFile, resourcePath);
if (data != null && context != null && context.debug > 1) {
System.err.println("# Loaded " + resourcePath + " from downloaded zip file.");
}
} catch (IOException e) {
if (context.debug > 1)
System.err.println("# Failed to load " + resourcePath + " from downloaded zip file.");
}
return data;
}
byte[] readURL(URL url) throws IOException {
byte[] data;
InputStream in = null;
try {
URLConnection c = url.openConnection();
c.setAllowUserInteraction(false);
in = c.getInputStream();
int len = c.getContentLength();
data = new byte[(len == -1) ? 4096 : len];
int total = 0, n;
while ((n = in.read(data, total, data.length - total)) >= 0) {
if ((total += n) == data.length) {
if (len < 0) {
byte newdata[] = new byte[total * 2];
System.arraycopy(data, 0, newdata, 0, total);
data = newdata;
} else {
break;
}
}
}
} finally {
if (in != null) {
in.close();
}
}
return data;
}
}