350 lines
14 KiB
Java
350 lines
14 KiB
Java
/*
|
|
* Copyright (C) 2009-2013 moparisthebest
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Official forums are http://www.moparscape.org/smf/
|
|
* Email me at admin@moparisthebest.com.
|
|
*/
|
|
|
|
package org.moparscape.classloader;
|
|
|
|
import org.moparscape.Debug;
|
|
import org.moparscape.res.impl.Downloader;
|
|
|
|
import java.io.*;
|
|
import java.net.URL;
|
|
import java.net.URLClassLoader;
|
|
import java.security.ProtectionDomain;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
import java.util.jar.JarEntry;
|
|
import java.util.jar.JarInputStream;
|
|
import java.util.zip.CRC32;
|
|
import java.util.zip.CheckedOutputStream;
|
|
import java.util.zip.GZIPInputStream;
|
|
|
|
/**
|
|
* This is a class loader that loads classes from jars on the filesystem, optionally checking the CRC of the classes and
|
|
* grabbing new updated jars if the CRC doesn't match.
|
|
* <p/>
|
|
*/
|
|
public class CRCClassLoader extends URLClassLoader {
|
|
|
|
public static final String[] illegalPackages = new String[]{"java.", "sun.", "javax."};
|
|
|
|
private Map<String, byte[]> classes = new HashMap<String, byte[]>();
|
|
private long crcVal = 0;
|
|
private boolean fileExists = false;
|
|
private ClassLoader parent = null;
|
|
private ProtectionDomain pd = null;
|
|
|
|
private int classesLoaded = 0;
|
|
|
|
/**
|
|
* Reads the jar file and calculates the CRC. Sets up the class.
|
|
* Responsibility of checking the CRC is the user's
|
|
*
|
|
* @param jarFileLoc The location of the jar file to load on the disk
|
|
*/
|
|
public CRCClassLoader(String jarFileLoc) throws IOException {
|
|
this(jarFileLoc, null);
|
|
}
|
|
|
|
public CRCClassLoader(String jarFileLoc, Class parent) throws IOException {
|
|
//super(new URL[]{new URL("file://" + jarFileLoc)}, parent == null ? null : parent.getClassLoader());
|
|
super(new URL[]{new URL("file://" + jarFileLoc)}, null);
|
|
setup(jarFileLoc);
|
|
if (parent != null) {
|
|
this.parent = parent.getClassLoader();
|
|
this.pd = parent.getProtectionDomain();
|
|
}
|
|
}
|
|
|
|
public static CRCClassLoader newInstance(String jarFileLoc, String backupURL, long expectedCRC, boolean crcMismatchException) throws IOException {
|
|
return newInstance(jarFileLoc, backupURL, expectedCRC, null, crcMismatchException);
|
|
}
|
|
|
|
/**
|
|
* Reads the jar file and calculates the CRC. Sets up the class.
|
|
* If the provided CRC does not match, downloads the new jar file to
|
|
* the provided location and tries again. If it fails again, an IOException is thrown.
|
|
*
|
|
* @param jarFileLoc The location of the jar file to load on the disk
|
|
*/
|
|
public static CRCClassLoader newInstance(String jarFileLoc, String backupURL, long expectedCRC, Class parent, boolean crcMismatchException) throws IOException {
|
|
//super(new URL[]{}, parent == null ? null : parent.getClassLoader());
|
|
CRCClassLoader ret = null;
|
|
// wrap this one in a try / catch, so we can retry if it throws an exception
|
|
try {
|
|
ret = new CRCClassLoader(jarFileLoc, parent);
|
|
|
|
// check CRC
|
|
if (ret.successfullyLoaded(expectedCRC))
|
|
return ret;
|
|
} catch (IOException e) {
|
|
//e.printStackTrace();
|
|
// if we don't have a backupURL, just go ahead and rethrow this exception, can't do anything more
|
|
if (backupURL == null)
|
|
throw e;
|
|
}
|
|
|
|
if (backupURL != null) {
|
|
|
|
//if(ret == null || ret.getCRC() != 0)
|
|
if (ret != null && ret.fileExists())
|
|
System.out.println("CRC checksum failed, downloading new file.");
|
|
|
|
// URLConnection uc = new URL(backupURL).openConnection();
|
|
// InputStream in = uc.getInputStream();
|
|
// FileOutputStream fos = new FileOutputStream(jarFileLoc);
|
|
// byte[] buffer = new byte[1024];
|
|
// int len;
|
|
// while ((len = in.read(buffer)) >= 0)
|
|
// fos.write(buffer, 0, len);
|
|
// fos.flush();
|
|
// in.close();
|
|
// fos.close();
|
|
|
|
// use Update instead
|
|
//new Update(backupURL, jarFileLoc, true);
|
|
// use ResourceGrabber
|
|
int jarWait = org.moparscape.res.ResourceGrabber.getRG().download(backupURL, jarFileLoc);
|
|
if (org.moparscape.res.ResourceGrabber.getRG().waitCatch(jarWait)) {
|
|
jarFileLoc = org.moparscape.res.ResourceGrabber.getRG().firstFileEndsWithIgnoreCase(jarWait, "jar.gz", "jar");
|
|
org.moparscape.res.ResourceGrabber.getRG().freeResources(jarWait);
|
|
}
|
|
Debug.debug("new jarFileLoc: " + jarFileLoc);
|
|
ret = new CRCClassLoader(jarFileLoc, parent);
|
|
|
|
}
|
|
if (!ret.successfullyLoaded(expectedCRC) && ret.getCRC() != 0) {
|
|
String s = "CRC checksum failed. crc:" + ret.getCRC() + " expected:" + expectedCRC;
|
|
if (crcMismatchException)
|
|
throw new IOException(s);
|
|
else
|
|
System.err.println(s);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
public void addJar(String jarFileLoc) throws IOException {
|
|
//this.setup(jarFileLoc, false);
|
|
super.addURL(new URL("file://" + jarFileLoc));
|
|
}
|
|
|
|
private void setup(String jarFileLoc) throws IOException {
|
|
this.setup(jarFileLoc, true);
|
|
}
|
|
|
|
private void setup(String jarFileLoc, boolean updateCRC) throws IOException {
|
|
if (jarFileLoc == null)
|
|
return;
|
|
File f = new File(jarFileLoc);
|
|
fileExists = f.exists() && f.isFile() && f.canRead();
|
|
if (!fileExists) {
|
|
Debug.debug("Jar file doesn't exist: " + jarFileLoc);
|
|
return;
|
|
}
|
|
Debug.debug("Loading classes from jar: " + f.getAbsolutePath());
|
|
|
|
if (updateCRC)
|
|
classes = new HashMap<String, byte[]>();
|
|
|
|
CRC32 crc = null;
|
|
if (updateCRC) {
|
|
crcVal = 0;
|
|
crc = new CRC32();
|
|
}
|
|
/*
|
|
JarFile jf = new JarFile(f);
|
|
Enumeration entries = jf.entries();
|
|
while (entries.hasMoreElements()) {
|
|
JarEntry entry = (JarEntry) entries.nextElement();
|
|
*/
|
|
InputStream is = new FileInputStream(f);
|
|
if (jarFileLoc.toLowerCase().endsWith(".gz"))
|
|
is = new GZIPInputStream(is);
|
|
JarInputStream in = new JarInputStream(is);
|
|
JarEntry entry;
|
|
while ((entry = in.getNextJarEntry()) != null) {
|
|
if (entry.getName().endsWith(".class")) {
|
|
//InputStream in = jf.getInputStream(entry);
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
OutputStream out = baos;
|
|
if (updateCRC)
|
|
//in = new CheckedInputStream(in, crc);
|
|
out = new CheckedOutputStream(out, crc);
|
|
|
|
Downloader.writeStream(in, out);
|
|
|
|
String className = entry.getName().substring(0, entry.getName().lastIndexOf(".")).replaceAll("/", ".");
|
|
//if (updateCRC) System.out.println("class name: " + className);
|
|
// save class
|
|
classes.put(className, baos.toByteArray());
|
|
++classesLoaded;
|
|
}
|
|
}
|
|
//jf.close();
|
|
in.close();
|
|
|
|
if (updateCRC)
|
|
crcVal = crc.getValue();
|
|
}
|
|
|
|
public long getCRC() {
|
|
return crcVal;
|
|
}
|
|
|
|
public boolean fileExists() {
|
|
return this.fileExists;
|
|
}
|
|
|
|
public boolean successfullyLoaded() {
|
|
//System.out.println("classesLoaded: "+classesLoaded);
|
|
return this.successfullyLoaded(0);
|
|
}
|
|
|
|
public boolean successfullyLoaded(long expectedCRC) {
|
|
if (expectedCRC == 0)
|
|
return classesLoaded > 0;
|
|
else
|
|
return crcVal == expectedCRC;
|
|
//return (expectedCRC == 0 && classesLoaded > 0) || crcVal == expectedCRC;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "CRCClassLoader{" +
|
|
"crcVal=" + crcVal +
|
|
", classesLoaded=" + classesLoaded +
|
|
", fileExists=" + fileExists +
|
|
//", successfullyLoaded(0)=" + successfullyLoaded(0) +
|
|
", classes" + classes +
|
|
'}';
|
|
}
|
|
|
|
/*
|
|
protected synchronized Class<?> loadClass(String name, boolean resolve)
|
|
throws ClassNotFoundException {
|
|
System.out.println("loadClass called: "+name);
|
|
// First, check if the class has already been loaded
|
|
Class c = findLoadedClass(name);
|
|
System.out.println("past findLoadedClass: "+c);
|
|
if (c == null) {
|
|
try {
|
|
c = super.loadClass(name, false);
|
|
System.out.println("past super.loadClass: "+c);
|
|
} catch (ClassNotFoundException e) {
|
|
// ClassNotFoundException thrown if class not found
|
|
// from the non-null parent class loader
|
|
}
|
|
if (c == null) {
|
|
// If still not found, then invoke findClass in order
|
|
// to find the class.
|
|
c = findClass(name);
|
|
System.out.println("past findClass: "+c);
|
|
}
|
|
}
|
|
if (resolve) {
|
|
resolveClass(c);
|
|
}
|
|
System.out.println("returning: "+c);
|
|
return c;
|
|
}*/
|
|
|
|
// strange errors
|
|
/*
|
|
32-bit:
|
|
past findLoadedClass: null
|
|
Exception in thread "thread applet-org.moparscape.Applet-1" java.lang.ClassFormatError: Incompatible magic value 1011373133 in class file client
|
|
at java.lang.ClassLoader.defineClass1(Native Method)
|
|
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
|
|
at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
|
|
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
|
|
at sun.plugin2.applet.Applet2ClassLoader.findClass(Applet2ClassLoader.java:141)
|
|
at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
|
|
at java.lang.ClassLoader.loadClass(ClassLoader.java:296)
|
|
at org.moparscape.classloader.CRCClassLoader.loadClass(CRCClassLoader.java:238)
|
|
at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
|
|
at org.moparscape.MainPanel.init(MainPanel.java:415)
|
|
at org.moparscape.Applet.init(Applet.java:42)
|
|
at sun.plugin2.applet.Plugin2Manager$AppletExecutionRunnable.run(Plugin2Manager.java:1579)
|
|
at java.lang.Thread.run(Thread.java:619)
|
|
|
|
64-bit:
|
|
past findLoadedClass: null
|
|
java.lang.SecurityException: trusted loader attempted to load sandboxed resource from http://mopar.moparscape.org/
|
|
at com.sun.deploy.security.CPCallbackHandler$ParentCallback.check(CPCallbackHandler.java:311)
|
|
at com.sun.deploy.security.CPCallbackHandler$ParentCallback.access$1500(CPCallbackHandler.java:123)
|
|
at com.sun.deploy.security.CPCallbackHandler$ChildElement.checkResource(CPCallbackHandler.java:480)
|
|
at sun.plugin2.applet.Plugin2ClassLoader.checkResource(Plugin2ClassLoader.java:856)
|
|
at sun.plugin2.applet.Applet2ClassLoader.findClass(Applet2ClassLoader.java:245)
|
|
at sun.plugin2.applet.Plugin2ClassLoader.loadClass0(Plugin2ClassLoader.java:250)
|
|
at sun.plugin2.applet.Plugin2ClassLoader.loadClass(Plugin2ClassLoader.java:180)
|
|
at sun.plugin2.applet.Plugin2ClassLoader.loadClass(Plugin2ClassLoader.java:161)
|
|
at java.lang.ClassLoader.loadClass(ClassLoader.java:295)
|
|
at org.moparscape.classloader.CRCClassLoader.loadClass(CRCClassLoader.java:238)
|
|
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
|
|
at org.moparscape.MainPanel.init(MainPanel.java:415)
|
|
at org.moparscape.Applet.init(Applet.java:42)
|
|
at sun.plugin2.applet.Plugin2Manager$AppletExecutionRunnable.run(Plugin2Manager.java:1637)
|
|
at java.lang.Thread.run(Thread.java:662)
|
|
*/
|
|
|
|
/**
|
|
* Is called by the ClassLoader when the requested class
|
|
* is not found in its cache. The parent is only used when
|
|
* this is ran as an applet, strangely.
|
|
*
|
|
* @param name The name of the class
|
|
*/
|
|
public Class<?> findClass(String name) throws ClassNotFoundException {
|
|
//System.out.println("CRCClassLoader: Requesting class '" + name + "'");
|
|
// let's make sure we don't provide a class in an illegal package
|
|
// only let the parent load it (probably system classloader, which won't allow a bad package anyway)
|
|
for (String pack : illegalPackages)
|
|
if (name.startsWith(pack)) {
|
|
// free the memory if it is taking up any
|
|
classes.remove(name);
|
|
// if the parent is null, we have no choice
|
|
if (parent == null)
|
|
throw new ClassNotFoundException("Class '" + name + "' is in illegal package '" + pack + "'");
|
|
// otherwise let the parent worry about it
|
|
return parent.loadClass(name);
|
|
}
|
|
|
|
// first, try to load them from the classes we have on hand
|
|
byte[] classBytes = classes.get(name);
|
|
if (classBytes != null) {
|
|
// free the memory
|
|
classes.remove(name);
|
|
//System.out.printf("removed class: '%s', size: '%d'\n", name, classes.size());
|
|
// return the class
|
|
return defineClass(name, classBytes, 0, classBytes.length, pd);
|
|
}
|
|
// now let's check the parent, if we have one
|
|
if (parent != null)
|
|
try {
|
|
return parent.loadClass(name);
|
|
} catch (Exception e) {
|
|
// ignore
|
|
}
|
|
|
|
// if we still haven't found it, ask super, and if it's not found there, they will throw the exception for us
|
|
return super.findClass(name);
|
|
}
|
|
|
|
}
|