/*
This file is part of leafdigital leafChat.
leafChat is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
leafChat 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with leafChat. If not, see <http://www.gnu.org/licenses/>.
Copyright 2011 Samuel Marshall.
*/
package leafchat.core;
import java.io.*;
import java.net.*;
import java.util.HashMap;
import java.util.jar.*;
/**
* Classloader that loads resources from a jar file into memory, without
* keeping the file open.
*/
public class SafeJarClassLoader extends ClassLoader
{
private String fileName, shortName;
private HashMap<String, byte[]> entries = new HashMap<String, byte[]>();
private byte[] CLASS_RELEASED = "!!!released!!!".getBytes();
private SafeJarURLStreamHandler handler =
new SafeJarURLStreamHandler();
private static class SafeJarURLConnection extends URLConnection
{
private byte[] data;
private SafeJarURLConnection(URL u, byte[] data)
{
super(u);
this.data = data;
}
@Override
public void connect() throws IOException
{
}
@Override
public InputStream getInputStream() throws IOException
{
return new ByteArrayInputStream(data);
}
}
private class SafeJarURLStreamHandler extends URLStreamHandler
{
@Override
protected URLConnection openConnection(URL u) throws IOException
{
// Check path and convert it into entry name
String path = u.getPath();
if(!path.startsWith("/" + shortName + "/"))
{
throw new IOException("SafeJarURLStreamHandler: invalid URL "
+ u + " for " + shortName);
}
path = path.substring(shortName.length() + 2);
// Look for entry with that name
byte[] data = entries.get(path);
if(data == null)
{
throw new IOException("SafeJarURLStreamHandler: data not found at URL "
+ u + " for " + shortName);
}
return new SafeJarURLConnection(u, data);
}
}
/**
* Constructs classloader.
* @param file Jar file
* @param parent Parent classloader
* @throws IOException If jar file cannot be read
*/
public SafeJarClassLoader(File file, ClassLoader parent) throws IOException
{
super(parent);
// Remember name
fileName = file.getAbsolutePath();
shortName = file.getName();
// Open jar file
FileInputStream fileInput = new FileInputStream(file);
try
{
JarInputStream jarInput = new JarInputStream(fileInput);
byte[] buffer = new byte[65536];
// Read all entries into RAM
while(true)
{
JarEntry entry = jarInput.getNextJarEntry();
if(entry == null)
{
break;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
while(true)
{
int read = jarInput.read(buffer);
if(read == -1)
{
break;
}
out.write(buffer, 0, read);
}
entries.put(entry.getName(), out.toByteArray());
}
jarInput.close();
}
finally
{
fileInput.close(); // Does nothing if jar was closed OK above
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException
{
// Get filename
String entryName = name.replace('.', '/') + ".class";
// Find file data
byte[] data = entries.get(entryName);
if(data == null)
{
throw new ClassNotFoundException("Class not found: " + name + " in "
+ fileName);
}
if(data == CLASS_RELEASED)
{
throw new Error("Unable to find class multiple times: " + name + " in "
+ fileName);
}
// Define class, free RAM, and return
Class<?> c = defineClass(name, data, 0, data.length);
entries.put(entryName, CLASS_RELEASED);
return c;
}
@Override
protected URL findResource(String name)
{
// Find file data
if (!entries.containsKey(name))
{
return null;
}
try
{
return new URL("safejar", "", 0, "/" + shortName + "/" + name, handler);
}
catch(MalformedURLException e)
{
throw new Error(e);
}
}
/**
* @return Array listing names of all entries in jar
*/
public String[] getEntryNames()
{
return entries.keySet().toArray(
new String[entries.keySet().size()]);
}
/**
* @param name Named entry
* @return Entry with given name, or null if none
*/
public byte[] getEntry(String name)
{
return entries.get(name);
}
}