Rename package jet -> kotlin in preloader and instrumentation

org.jetbrains.jet.preloading -> org.jetbrains.kotlin.preloading
This commit is contained in:
Alexander Udalov
2015-01-03 15:31:18 +03:00
parent e73ffe76a0
commit 1bf3ca2e26
31 changed files with 62 additions and 62 deletions
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.preloading;
public interface ClassCondition {
boolean accept(String className);
}
@@ -0,0 +1,32 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.preloading;
import java.io.File;
@SuppressWarnings("UnusedParameters")
public abstract class ClassHandler {
public byte[] instrument(String resourceName, byte[] data) {
return data;
}
public void beforeDefineClass(String name, int sizeInBytes) {}
public void afterDefineClass(String name) {}
public void beforeLoadJar(File jarFile) {}
public void afterLoadJar(File jarFile) {}
}
@@ -0,0 +1,183 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.preloading;
import java.io.*;
import java.util.*;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
@SuppressWarnings("unchecked")
public class ClassPreloadingUtils {
/**
* Creates a class loader that loads all classes from {@code jarFiles} into memory to make loading faster (avoid skipping through zip archives).
*
* 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 parentClassLoader parent class loader
* @param handler handler to be notified on class definitions done by this class loader, or null
* @param classesToLoadByParent condition to load some classes via parent class loader
* @return a class loader that reads classes from memory
* @throws IOException on from reading the jar
*/
public static ClassLoader preloadClasses(
Collection<File> jarFiles,
int classCountEstimation,
ClassLoader parentClassLoader,
ClassCondition classesToLoadByParent,
ClassHandler handler
) throws IOException {
Map<String, Object> entries = loadAllClassesFromJars(jarFiles, classCountEstimation, handler);
Collection<File> classpath = mergeClasspathFromManifests(entries);
if (!classpath.isEmpty()) {
parentClassLoader = preloadClasses(classpath, classCountEstimation, parentClassLoader, null, handler);
}
return new MemoryBasedClassLoader(classesToLoadByParent, parentClassLoader, entries, handler);
}
public static ClassLoader preloadClasses(
Collection<File> jarFiles, int classCountEstimation, ClassLoader parentClassLoader, ClassCondition classesToLoadByParent
) throws IOException {
return preloadClasses(jarFiles, classCountEstimation, parentClassLoader, classesToLoadByParent, null);
}
private static Collection<File> mergeClasspathFromManifests(Map<String, Object> preloadedResources) throws IOException {
Object manifest = preloadedResources.get(JarFile.MANIFEST_NAME);
if (manifest instanceof ResourceData) {
return extractManifestClasspath((ResourceData) manifest);
}
else if (manifest instanceof ArrayList) {
List<File> result = new ArrayList<File>();
for (ResourceData data : (ArrayList<ResourceData>) manifest) {
result.addAll(extractManifestClasspath(data));
}
return result;
}
else {
assert manifest == null : "Resource map should contain ResourceData or ArrayList<ResourceData>: " + manifest;
return Collections.emptyList();
}
}
private static Collection<File> extractManifestClasspath(ResourceData manifestData) throws IOException {
Manifest manifest = new Manifest(new ByteArrayInputStream(manifestData.bytes));
String classpathSpaceSeparated = (String) manifest.getMainAttributes().get(Attributes.Name.CLASS_PATH);
if (classpathSpaceSeparated == null) return Collections.emptyList();
Collection<File> classpath = new ArrayList<File>(1);
for (String jar : classpathSpaceSeparated.split(" ")) {
if (".".equals(jar)) continue;
if (!jar.endsWith(".jar")) {
throw new UnsupportedOperationException("Class-Path attribute should only contain paths to JAR files: " + jar);
}
classpath.add(new File(manifestData.jarFile.getParent(), jar));
}
return classpath;
}
/**
* @return a map of name to resources. Each value is either a ResourceData if there's only one instance (in the vast majority of cases)
* or a non-empty ArrayList of ResourceData if there's many
*/
private static Map<String, Object> loadAllClassesFromJars(
Collection<File> jarFiles,
int classNumberEstimate,
ClassHandler handler
) throws IOException {
// 0.75 is HashMap.DEFAULT_LOAD_FACTOR
Map<String, Object> resources = new HashMap<String, Object>((int) (classNumberEstimate / 0.75));
for (File jarFile : jarFiles) {
if (handler != null) {
handler.beforeLoadJar(jarFile);
}
FileInputStream fileInputStream = new FileInputStream(jarFile);
try {
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;
int size = (int) entry.getSize();
int effectiveSize = size < 0 ? 32 : size;
ByteArrayOutputStream bytes = new ByteArrayOutputStream(effectiveSize);
int count;
while ((count = stream.read(buffer)) > 0) {
bytes.write(buffer, 0, count);
}
String name = entry.getName();
byte[] data = bytes.toByteArray();
if (handler != null) {
data = handler.instrument(name, data);
}
ResourceData resourceData = new ResourceData(jarFile, name, data);
Object previous = resources.get(name);
if (previous == null) {
resources.put(name, resourceData);
}
else if (previous instanceof ResourceData) {
List<ResourceData> list = new ArrayList<ResourceData>();
list.add((ResourceData) previous);
list.add(resourceData);
resources.put(name, list);
}
else {
assert previous instanceof ArrayList :
"Resource map should contain ResourceData or ArrayList<ResourceData>: " + name;
((ArrayList<ResourceData>) previous).add(resourceData);
}
}
}
finally {
try {
fileInputStream.close();
}
catch (IOException e) {
// Ignore
}
}
if (handler != null) {
handler.afterLoadJar(jarFile);
}
}
for (Object value : resources.values()) {
if (value instanceof ArrayList) {
((ArrayList) value).trimToSize();
}
}
return resources;
}
}
@@ -0,0 +1,136 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.preloading;
import java.io.IOException;
import java.net.URL;
import java.util.*;
@SuppressWarnings("unchecked")
public class MemoryBasedClassLoader extends ClassLoader {
private final ClassCondition classesToLoadByParent;
private final ClassLoader parent;
private final Map<String, Object> preloadedResources;
private final ClassHandler handler;
public MemoryBasedClassLoader(
ClassCondition classesToLoadByParent,
ClassLoader parent,
Map<String, Object> preloadedResources,
ClassHandler handler
) {
super(null);
this.classesToLoadByParent = classesToLoadByParent;
this.parent = parent;
this.preloadedResources = preloadedResources;
this.handler = handler;
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (classesToLoadByParent != null && classesToLoadByParent.accept(name)) {
if (parent == null) {
return super.loadClass(name);
}
try {
return parent.loadClass(name);
}
catch (ClassNotFoundException e) {
return super.loadClass(name);
}
}
// Look in this class loader and then in the parent one
Class<?> aClass = super.loadClass(name);
if (aClass == null) {
if (parent == null) {
throw new ClassNotFoundException("Class not available in preloader: " + name);
}
return parent.loadClass(name);
}
return aClass;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String internalName = name.replace('.', '/').concat(".class");
Object resources = preloadedResources.get(internalName);
if (resources == null) return null;
ResourceData resourceData = resources instanceof ResourceData
? ((ResourceData) resources)
: ((List<ResourceData>) resources).get(0);
int sizeInBytes = resourceData.bytes.length;
if (handler != null) {
handler.beforeDefineClass(name, sizeInBytes);
}
Class<?> definedClass = defineClass(name, resourceData.bytes, 0, sizeInBytes);
if (handler != null) {
handler.afterDefineClass(name);
}
return definedClass;
}
@Override
public URL getResource(String name) {
URL resource = super.getResource(name);
if (resource == null && parent != null) {
return parent.getResource(name);
}
return resource;
}
@Override
protected URL findResource(String name) {
Enumeration<URL> resources = findResources(name);
return resources.hasMoreElements() ? resources.nextElement() : null;
}
@Override
public Enumeration<URL> getResources(String name) throws IOException {
Enumeration<URL> resources = super.getResources(name);
if (!resources.hasMoreElements() && parent != null) {
return parent.getResources(name);
}
return resources;
}
@Override
protected Enumeration<URL> findResources(String name) {
Object resources = preloadedResources.get(name);
if (resources == null) {
return Collections.enumeration(Collections.<URL>emptyList());
}
else if (resources instanceof ResourceData) {
return Collections.enumeration(Collections.singletonList(((ResourceData) resources).getURL()));
}
else {
assert resources instanceof ArrayList : "Resource map should contain ResourceData or ArrayList<ResourceData>: " + name;
List<ResourceData> resourceDatas = (ArrayList<ResourceData>) resources;
List<URL> urls = new ArrayList<URL>(resourceDatas.size());
for (ResourceData data : resourceDatas) {
urls.add(data.getURL());
}
return Collections.enumeration(urls);
}
}
}
@@ -0,0 +1,192 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.preloading;
import org.jetbrains.kotlin.preloading.instrumentation.Instrumenter;
import java.io.File;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
public class Preloader {
public static final int PRELOADER_ARG_COUNT = 4;
private static final String INSTRUMENT_PREFIX = "instrument=";
public static void main(String[] args) throws Exception {
if (args.length < PRELOADER_ARG_COUNT) {
printUsageAndExit();
}
List<File> files = parseClassPath(args[0]);
String mainClassCanonicalName = args[1];
int classNumber;
try {
classNumber = Integer.parseInt(args[2]);
}
catch (NumberFormatException e) {
System.out.println(e.getMessage());
printUsageAndExit();
return;
}
String modeStr = args[3];
final Mode mode = parseMode(modeStr);
URL[] instrumentersClasspath = parseInstrumentersClasspath(mode, modeStr);
final long startTime = System.nanoTime();
ClassLoader parent = Preloader.class.getClassLoader();
ClassLoader withInstrumenter = instrumentersClasspath.length > 0 ? new URLClassLoader(instrumentersClasspath, parent) : parent;
final Handler handler = getHandler(mode, withInstrumenter);
ClassLoader preloaded = ClassPreloadingUtils.preloadClasses(files, classNumber, withInstrumenter, null, handler);
Class<?> mainClass = preloaded.loadClass(mainClassCanonicalName);
Method mainMethod = mainClass.getMethod("main", String[].class);
Runtime.getRuntime().addShutdownHook(
new Thread(new Runnable() {
@Override
public void run() {
if (mode != Mode.NO_TIME) {
System.out.println();
System.out.println("=== Preloader's measurements: ");
long dt = System.nanoTime() - startTime;
System.out.format("Total time: %.3fs\n", dt / 1e9);
}
handler.done();
}
})
);
mainMethod.invoke(0, new Object[] {Arrays.copyOfRange(args, PRELOADER_ARG_COUNT, args.length)});
}
private static URL[] parseInstrumentersClasspath(Mode mode, String modeStr)
throws MalformedURLException {
URL[] instrumentersClasspath;
if (mode == Mode.INSTRUMENT) {
List<File> instrumentersClassPathFiles = parseClassPath(getClassPath(modeStr));
instrumentersClasspath = new URL[instrumentersClassPathFiles.size()];
for (int i = 0; i < instrumentersClassPathFiles.size(); i++) {
File file = instrumentersClassPathFiles.get(i);
instrumentersClasspath[i] = file.toURI().toURL();
}
}
else {
instrumentersClasspath = new URL[0];
}
return instrumentersClasspath;
}
private static String getClassPath(String modeStr) {
return modeStr.substring(INSTRUMENT_PREFIX.length());
}
private static List<File> parseClassPath(String classpath) {
String[] paths = classpath.split("\\" + File.pathSeparator);
List<File> files = new ArrayList<File>(paths.length);
for (String path : paths) {
File file = new File(path);
if (!file.exists()) {
System.out.println("File does not exist: " + file);
printUsageAndExit();
}
files.add(file);
}
return files;
}
private static Handler getHandler(Mode mode, ClassLoader withInstrumenter) {
if (mode == Mode.NO_TIME) return new Handler();
final Instrumenter instrumenter = mode == Mode.INSTRUMENT ? loadInstrumenter(withInstrumenter) : Instrumenter.DO_NOTHING;
final int[] counter = new int[1];
final int[] size = new int[1];
return new Handler() {
@Override
public void beforeDefineClass(String name, int sizeInBytes) {
counter[0]++;
size[0] += sizeInBytes;
}
@Override
public void done() {
System.out.println();
System.out.println("Loaded classes: " + counter[0]);
System.out.println("Loaded classes size: " + size[0]);
System.out.println();
instrumenter.dump(System.out);
}
@Override
public byte[] instrument(String resourceName, byte[] data) {
return instrumenter.instrument(resourceName, data);
}
};
}
private static Instrumenter loadInstrumenter(ClassLoader withInstrumenter) {
ServiceLoader<Instrumenter> loader = ServiceLoader.load(Instrumenter.class, withInstrumenter);
Iterator<Instrumenter> instrumenters = loader.iterator();
if (instrumenters.hasNext()) {
Instrumenter instrumenter = instrumenters.next();
if (instrumenters.hasNext()) {
System.err.println("PRELOADER WARNING: Only the first instrumenter is used: " + instrumenter.getClass());
}
return instrumenter;
}
else {
System.err.println("PRELOADER WARNING: No instrumenters found");
return Instrumenter.DO_NOTHING;
}
}
private enum Mode {
NO_TIME,
TIME,
INSTRUMENT
}
private static Mode parseMode(String arg) {
if ("time".equals(arg)) return Mode.TIME;
if ("notime".equals(arg)) return Mode.NO_TIME;
if (arg.startsWith(INSTRUMENT_PREFIX)) return Mode.INSTRUMENT;
System.out.println("Unrecognized argument: " + arg);
printUsageAndExit();
return Mode.NO_TIME;
}
private static void printUsageAndExit() {
System.out.println("Usage: Preloader <paths to jars> <main class> <class number estimate> <notime|time|instrument=<instrumenters class path>> <parameters to pass to the main class>");
System.exit(1);
}
private static class Handler extends ClassHandler {
public void done() {}
}
}
@@ -0,0 +1,62 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.preloading;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
public final class ResourceData {
public final File jarFile;
public final String resourceName;
public 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;
}
}
}
@@ -0,0 +1,37 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.preloading.instrumentation;
import java.io.PrintStream;
public interface Instrumenter {
Instrumenter DO_NOTHING = new Instrumenter() {
@Override
public byte[] instrument(String resourceName, byte[] data) {
return data;
}
@Override
public void dump(PrintStream out) {
// Do nothing
}
};
byte[] instrument(String resourceName, byte[] data);
void dump(PrintStream out);
}