/*******************************************************************************
* Copyright (c) 2015 Willink Transformations and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* E.D.Willink - initial API and implementation
*******************************************************************************/
package org.eclipse.ocl.examples.codegen.dynamic;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
/**
* ExplicitClassLoader supports loading all classes sharing a qualifiedClassNamePrefix from an explicitClassPath.
* This may be used to load classes not on the normal classpath or to reload classes that may have changed since
* previously loaded.
* <p>
* The technical approach of:
* <br>load *.class as byte[],
* <br>defineClass to transform byte[] to Class<?>
* <p>
* is motivated by http://www.toptal.com/java/java-wizardry-101-a-guide-to-java-class-reloading.
*/
class ExplicitClassLoader extends ClassLoader
{
protected final @NonNull File explicitClassPath;
protected final @NonNull String qualifiedClassNamePrefix;
private final @NonNull Map<String, Class<?>> hitsAndMisses = new HashMap<String, Class<?>>(); // Miss signalled by ExplicitClassLoader.class
public ExplicitClassLoader(@NonNull File explicitClassPath, @NonNull String qualifiedClassNamePrefix) {
this.explicitClassPath = explicitClassPath;
this.qualifiedClassNamePrefix = qualifiedClassNamePrefix;
}
/**
* Re-implement to load any class sharing qualifiedClassNamePrefix from the explicitClassPath.
* Other classes are loaded normally.
*/
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (name.startsWith(qualifiedClassNamePrefix)) {
Class<?> hitOrMiss = hitsAndMisses.get(name);
if (hitOrMiss != null) {
if (hitOrMiss == ExplicitClassLoader.class) {
throw new ClassNotFoundException("Attempted reload of '" + name + "' failed");
}
else {
return hitOrMiss;
}
}
try {
hitOrMiss = loadExplicitClass(name, resolve);
hitsAndMisses.put(name, hitOrMiss);
return hitOrMiss;
} catch (IOException e) {
hitsAndMisses.put(name, ExplicitClassLoader.class);
throw new ClassNotFoundException(e.getMessage(), e);
}
}
else {
return super.loadClass(name, resolve);
}
}
/**
* Load the class whose Java name is qualifiedClassName and whose class file can be found below explicitClassPath.
* This method always loads the class and so ignores any previously cached loads.
*/
private @Nullable Class<?> loadExplicitClass(@NonNull String qualifiedClassName, boolean resolve) throws ClassNotFoundException, IOException {
String filePath = qualifiedClassName.replaceAll("\\.", "/") + ".class";
File classFile = new File(explicitClassPath, filePath);
FileInputStream inputStream = new FileInputStream(classFile);
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
try {
int bytes;
byte[] byteArray = new byte[16384];
while ((bytes = inputStream.read(byteArray)) >= 0) {
byteStream.write(byteArray, 0, bytes);
}
}
finally {
try {
inputStream.close();
} catch (IOException e) {}
}
byte[] classBytes = byteStream.toByteArray();
Class<?> theClass = defineClass(qualifiedClassName, classBytes, 0, classBytes.length);
if (theClass == null) {
return null;
}
Package thePackage = theClass.getPackage();
if (thePackage == null) {
String packageName = qualifiedClassName.replaceAll("\\.\\w+$", "");
definePackage(packageName, null, null, null, null, null, null, null);
}
if (resolve) {
resolveClass(theClass);
}
return theClass;
}
}