package PluginLoader.Implementation;
import EnvironmentPluginAPI.Exceptions.TechnicalException;
import ZeroTypes.Exceptions.ErrorMessages;
import PluginLoader.Interface.Exceptions.PluginNotReadableException;
import sun.misc.Launcher;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* This class helps with the loading of plugins
*/
class PluginHelper {
ClassLoader classLoader = null;
/**
* Returns a list of all jars in a directory. This is not only true for jars located directly in the
* directory, but also for jars in any sub-folder of the given folder.
*
* @param directory a valid directory string != null
* @return empty, if no jars found
*/
public List<String> findJarsRecursively(String directory) {
List<String> result = new LinkedList<String>();
File[] files = new File(directory).listFiles();
if (files != null) {
for (File file : files) {
if (file.isFile() && file.getPath().endsWith(".jar")) {
result.add(file.getAbsolutePath());
} else if (file.isDirectory()) {
result.addAll(findJarsRecursively(file.getPath()));
}
}
}
return result;
}
/**
* Creates a new classloader, in which all classes from the given jar are loaded.
*
* @param pathToJar the location of the jar != null
* @return the classloader
* @throws EnvironmentPluginAPI.Exceptions.TechnicalException
* if the jar is not readable
* @throws IllegalArgumentException if no path is found under the given path
*/
public Plugin loadJar(String pathToJar) throws TechnicalException, PluginNotReadableException {
List<Class> classes = listClassesFromJar(pathToJar);
//then make all classes of the plugin known to the class loader
for (Class<?> clazz : classes) {
try {
Class.forName(clazz.getName(), true, classLoader);
} catch (ClassNotFoundException e) {
throw new PluginNotReadableException(e.getMessage(), pathToJar);
}
}
Thread.currentThread().setContextClassLoader(classLoader);
return new Plugin(classLoader, classes);
}
/**
* Returns a list containing all class files found in the jar described by the parameter.
* The classes are not fully initialized but from now on known to the JVM.
*
* @param pathToJar the location of the jar != null
* @return empty, if no class files are in the jar
* @throws EnvironmentPluginAPI.Exceptions.TechnicalException
* if the jar is not readable
* @throws IllegalArgumentException if no path is found under the given path
*/
public List<Class> listClassesFromJar(String pathToJar) throws TechnicalException {
//create file access resources needed
List<Class> classes = new LinkedList<Class>();
try {
classLoader = new URLClassLoader(new URL[]{new File(pathToJar).toURI().toURL()}, Launcher.getLauncher().getClassLoader());
JarFile jarFile = new JarFile(pathToJar);
Enumeration<JarEntry> e = jarFile.entries();
// //result init and caching
JarEntry jarEntry;
String className;
//scan jar entries, convert name to canonical class name and try to load the class
while (e.hasMoreElements()) {
jarEntry = e.nextElement();
if (jarEntry.getName().endsWith(".class")) {
try {
//convert file name to canonical class name
className = jarEntry.getName().replaceAll("/", "\\.").replace(".class", "");
//load and add
classes.add(Class.forName(className, false, classLoader));
} catch (ClassNotFoundException ex) {
System.err.println("Class '" + jarEntry.getName().replaceAll("/", "\\.") + "' could not be found while loading jar:\n" + pathToJar);
}
}
}
return classes;
} catch (MalformedURLException e) {
throw new IllegalArgumentException(ErrorMessages.get("invalidJarPath") + e);
} catch (FileNotFoundException e) {
throw new IllegalArgumentException(ErrorMessages.get("unreadableJar") + e);
} catch (IOException e) {
throw new TechnicalException(ErrorMessages.get("invalidJarPath") + e);
}
}
/**
* Looks for a compatible constructors in a class object.
* <br/><br/>
* For the definition of compatible see isAssignable
*
* @param toExamine The class to look for the constructor in != null
* @param typesToLookFor the
* @return
*/
public Constructor findSuitableConstructor(Class toExamine, Class... typesToLookFor) {
Class[] argTypes;
for (Constructor ctor : toExamine.getConstructors()) {
argTypes = ctor.getParameterTypes();
if (isAssignable(argTypes, typesToLookFor)) {
return ctor;
}
}
return null;
}
/**
* Determines if all types in argTypes are compatible to the classes in types.
* <br/><br/>
* Compatible means:<br/>
* 1. Same length of list<br/>
* 2. Each type in argTypes is the same type or a subtype of the class in the same index in types
*
* @param argTypes a list of argument types
* @param types the types to look for
* @return see description
*/
public boolean isAssignable(Class[] argTypes, Class... types) {
if (argTypes.length != types.length) return false;
for (int i = 0; i < argTypes.length; i++) {
if (!types[i].isAssignableFrom(argTypes[i])) return false;
}
return true;
}
}