Allowing multiple jars

This commit is contained in:
Andrey Breslav
2013-04-22 15:54:40 +04:00
parent 2f65506d76
commit 2ce6d16433
2 changed files with 101 additions and 71 deletions
@@ -1,4 +1,4 @@
package org.jetbrains.jet.preloading;/* /*
* Copyright 2010-2013 JetBrains s.r.o. * Copyright 2010-2013 JetBrains s.r.o.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,6 +14,8 @@ package org.jetbrains.jet.preloading;/*
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.jet.preloading;
import sun.misc.CompoundEnumeration; import sun.misc.CompoundEnumeration;
import java.io.*; import java.io.*;
@@ -21,33 +23,32 @@ import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.net.URLStreamHandler; import java.net.URLStreamHandler;
import java.util.Collections; import java.util.*;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
public class ClassPreloadingUtils { public class ClassPreloadingUtils {
/** /**
* Creates a class loader that loads all classes from {@code jarFile} into memory to make loading faster (avoid skipping through zip archives). * Creates a class loader that loads all classes from {@code jarFiles} into memory to make loading faster (avoid skipping through zip archives).
* @param jarFile a jar to load all classes from *
* @param classCountEstimation an estimated number of classes in a the jar * NOTE: if many resources with the same name exist, only the first one will be loaded
*
* @param jarFiles jars to load all classes from
* @param classCountEstimation an estimated number of classes in a the jars
* @param parent (nullable) parent class loader * @param parent (nullable) parent class loader
* @return a class loader that reads classes from memory * @return a class loader that reads classes from memory
* @throws IOException on from reading the jar * @throws IOException on from reading the jar
*/ */
public static ClassLoader preloadClasses(File jarFile, int classCountEstimation, ClassLoader parent) throws IOException { public static ClassLoader preloadClasses(Collection<File> jarFiles, int classCountEstimation, ClassLoader parent) throws IOException {
Map<String, byte[]> entries = loadAllClassesFromJar(jarFile, classCountEstimation); Map<String, ResourceData> entries = loadAllClassesFromJars(jarFiles, classCountEstimation);
return createMemoryBasedClassLoader(parent, jarFile, entries); return createMemoryBasedClassLoader(parent, entries);
} }
private static ClassLoader createMemoryBasedClassLoader( private static ClassLoader createMemoryBasedClassLoader(
final ClassLoader parent, final ClassLoader parent,
final File jarFile, final Map<String, ResourceData> preloadedResources
final Map<String, byte[]> preloadedResources
) { ) {
return new ClassLoader(null) { return new ClassLoader(null) {
@Override @Override
@@ -63,38 +64,17 @@ public class ClassPreloadingUtils {
@Override @Override
protected Class<?> findClass(String name) throws ClassNotFoundException { protected Class<?> findClass(String name) throws ClassNotFoundException {
String internalName = name.replace('.', '/').concat(".class"); String internalName = name.replace('.', '/').concat(".class");
byte[] bytes = preloadedResources.get(internalName); ResourceData resourceData = preloadedResources.get(internalName);
if (bytes == null) return null; if (resourceData == null) return null;
return defineClass(name, bytes, 0, bytes.length); return defineClass(name, resourceData.bytes, 0, resourceData.bytes.length);
} }
@Override @Override
protected URL findResource(String name) { protected URL findResource(String name) {
final byte[] bytes = preloadedResources.get(name); ResourceData resourceData = preloadedResources.get(name);
if (bytes == null) return null; if (resourceData == null) return null;
return resourceData.getURL();
try {
String path = "file:" + jarFile + "!" + name;
return new URL("jar", null, 0, path, new URLStreamHandler() {
@Override
protected URLConnection openConnection(URL u) throws IOException {
return new URLConnection(u) {
@Override
public void connect() throws IOException {}
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(bytes);
}
};
}
});
}
catch (MalformedURLException e) {
e.printStackTrace();
return null;
}
} }
@Override @Override
@@ -103,41 +83,46 @@ public class ClassPreloadingUtils {
if (resource == null) { if (resource == null) {
return new CompoundEnumeration<URL>(new Enumeration[0]); return new CompoundEnumeration<URL>(new Enumeration[0]);
} }
// Only the first resource is loaded
return Collections.enumeration(Collections.singletonList(resource)); return Collections.enumeration(Collections.singletonList(resource));
} }
}; };
} }
private static Map<String, byte[]> loadAllClassesFromJar(File jarFile, int classNumberEstimate) throws IOException { private static Map<String, ResourceData> loadAllClassesFromJars(Collection<File> jarFiles, int classNumberEstimate) throws IOException {
Map<String, byte[]> classes = new HashMap<String, byte[]>(classNumberEstimate); Map<String, ResourceData> resources = new HashMap<String, ResourceData>(classNumberEstimate);
FileInputStream fileInputStream = new FileInputStream(jarFile); for (File jarFile : jarFiles) {
try { FileInputStream fileInputStream = new FileInputStream(jarFile);
byte[] buffer = new byte[10 * 1024];
ZipInputStream stream = new ZipInputStream(new BufferedInputStream(fileInputStream));
while (true) {
ZipEntry entry = stream.getNextEntry();
if (entry == null) break;
ByteArrayOutputStreamWithPublicArray bytes = new ByteArrayOutputStreamWithPublicArray((int) entry.getSize());
int count;
while ((count = stream.read(buffer)) > 0) {
bytes.write(buffer, 0, count);
}
if (!entry.isDirectory()) {
classes.put(entry.getName(), bytes.getBytes());
}
}
}
finally {
try { try {
fileInputStream.close(); byte[] buffer = new byte[10 * 1024];
ZipInputStream stream = new ZipInputStream(new BufferedInputStream(fileInputStream));
while (true) {
ZipEntry entry = stream.getNextEntry();
if (entry == null) break;
if (entry.isDirectory()) continue;
String name = entry.getName();
if (resources.containsKey(name)) continue; // Only the first resource is stored
ByteArrayOutputStreamWithPublicArray bytes = new ByteArrayOutputStreamWithPublicArray((int) entry.getSize());
int count;
while ((count = stream.read(buffer)) > 0) {
bytes.write(buffer, 0, count);
}
resources.put(name, new ResourceData(jarFile, name, bytes.getBytes()));
}
} }
catch (IOException e) { finally {
// Ignore try {
fileInputStream.close();
}
catch (IOException e) {
// Ignore
}
} }
} }
return classes; return resources;
} }
private static class ByteArrayOutputStreamWithPublicArray extends ByteArrayOutputStream { private static class ByteArrayOutputStreamWithPublicArray extends ByteArrayOutputStream {
@@ -150,4 +135,41 @@ public class ClassPreloadingUtils {
return buf; return buf;
} }
} }
private static class ResourceData {
private final File jarFile;
private final String resourceName;
private final byte[] bytes;
public ResourceData(File jarFile, String resourceName, byte[] bytes) {
this.jarFile = jarFile;
this.resourceName = resourceName;
this.bytes = bytes;
}
public URL getURL() {
try {
String path = "file:" + jarFile + "!" + resourceName;
return new URL("jar", null, 0, path, new URLStreamHandler() {
@Override
protected URLConnection openConnection(URL u) throws IOException {
return new URLConnection(u) {
@Override
public void connect() throws IOException {}
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(bytes);
}
};
}
});
}
catch (MalformedURLException e) {
e.printStackTrace();
return null;
}
}
}
} }
@@ -2,7 +2,9 @@ package org.jetbrains.jet.preloading;
import java.io.File; import java.io.File;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
public class Preloader { public class Preloader {
@@ -13,10 +15,16 @@ public class Preloader {
printUsageAndExit(); printUsageAndExit();
} }
File file = new File(args[0]); String classpath = args[0];
if (!file.exists()) { String[] paths = classpath.split("\\" + File.pathSeparator);
System.out.println("File does not exist: " + file); List<File> files = new ArrayList<File>(paths.length);
printUsageAndExit(); for (String path : paths) {
File file = new File(path);
if (!file.exists()) {
System.out.println("File does not exist: " + file);
printUsageAndExit();
}
files.add(file);
} }
String mainClassCanonicalName = args[1]; String mainClassCanonicalName = args[1];
@@ -36,7 +44,7 @@ public class Preloader {
ClassLoader parent = Preloader.class.getClassLoader(); ClassLoader parent = Preloader.class.getClassLoader();
ClassLoader preloaded = ClassPreloadingUtils.preloadClasses(file, classNumber, parent); ClassLoader preloaded = ClassPreloadingUtils.preloadClasses(files, classNumber, parent);
Class<?> mainClass = preloaded.loadClass(mainClassCanonicalName); Class<?> mainClass = preloaded.loadClass(mainClassCanonicalName);
Method mainMethod = mainClass.getMethod("main", String[].class); Method mainMethod = mainClass.getMethod("main", String[].class);
@@ -58,7 +66,7 @@ public class Preloader {
} }
private static void printUsageAndExit() { private static void printUsageAndExit() {
System.out.println("Usage: Preloader <path to jar> <main class> <class number estimate> <parameters to pass to the main class> <time|notime>"); System.out.println("Usage: Preloader <paths to jars> <main class> <class number estimate> <parameters to pass to the main class> <time|notime>");
System.exit(1); System.exit(1);
} }
} }