package org.kisst.util;
import org.kisst.cfg4j.BooleanSetting;
import org.kisst.cfg4j.CompositeSetting;
import org.kisst.cfg4j.MappedSetting;
import org.kisst.cfg4j.StringSetting;
import org.kisst.props4j.Props;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.Manifest;
public class JarLoader {
public static class ModuleSetting extends CompositeSetting {
public ModuleSetting(CompositeSetting parent, String name) { super(parent, name); }
public final BooleanSetting disabled = new BooleanSetting(this, "disabled", false);
}
public static class Settings extends CompositeSetting {
public Settings(CompositeSetting parent, String name) { super(parent, name); }
public final StringSetting moduleDirectory = new StringSetting(this, "directory", "./modules");
public final BooleanSetting checkDuplicates = new BooleanSetting(this, "checkDuplicates", true);
public final MappedSetting<ModuleSetting> module = new MappedSetting<ModuleSetting>(this, "module", ModuleSetting.class);
}
public static class ModuleInfo {
public final File file;
public final String mainClassname;
public final String version;
public File getFile() { return file;}
public Date getDate() { return new Date(file.lastModified()); }
public String getVersion() { return version; }
public String getMainClassname() { return mainClassname; }
public ModuleInfo(File f) {
this.file=f;
try {
@SuppressWarnings("deprecation")
URL url=new URL("jar:"+f.toURL()+"!/META-INF/MANIFEST.MF");
Manifest manifest = new Manifest(url.openStream());
this.mainClassname = manifest.getMainAttributes().getValue("Main-Class");
this.version = manifest.getMainAttributes().getValue("Implementation-Version");
} catch (IOException e) { throw new RuntimeException("Could not find module Main-Class in Manifest of "+f.getName(),e);}
}
}
private final File dir;
private final ArrayList<ModuleInfo> modules=new ArrayList<ModuleInfo>();
private final URLClassLoader loader;
public JarLoader(Props props, String topName) { this(new Settings(null,topName), props);}
public JarLoader(Settings settings, Props props) {
this.dir=new File(settings.moduleDirectory.get(props));
if (! dir.isDirectory())
throw new IllegalArgumentException(dir+" should be a directory");
for (File f:dir.listFiles()) {
if (f.isFile() && f.getName().endsWith(".jar")) {
ModuleInfo info = new ModuleInfo(f);
String modulename=info.mainClassname;
int pos=modulename.lastIndexOf('.');
if (pos>0)
modulename=modulename.substring(pos+1);
if (settings.module.get(modulename).disabled.get(props))
System.out.println("Skipping disabled module "+modulename);
else
modules.add(info);
}
}
int i=0;
URL[] urls = new URL[modules.size()];
boolean checkDuplicates=settings.checkDuplicates.get(props);
HashMap<String, ModuleInfo> alreadyLoadedModules = new HashMap<String, ModuleInfo>();
for (ModuleInfo m: modules) {
if (checkDuplicates) {
ModuleInfo info=alreadyLoadedModules.get(m.mainClassname);
if (info!=null)
throw new DuplicateModuleException(m, info);
alreadyLoadedModules.put(m.mainClassname, m);
}
try {
urls[i++]=m.file.toURI().toURL();
}
catch (MalformedURLException e) { throw new RuntimeException(e); }
}
loader = new URLClassLoader(urls);
}
public List<ModuleInfo> getModuleInfo() { return Collections.unmodifiableList(modules); }
public ClassLoader getClassLoader() { return loader; }
public Class<?> getClass(String name) {
try {
return Class.forName(name, true, loader);
}
catch (ClassNotFoundException e) { throw new RuntimeException(e); }
}
public class DuplicateModuleException extends RuntimeException {
private static final long serialVersionUID = 1L;
public final ModuleInfo mod1;
public final ModuleInfo mod2;
public DuplicateModuleException(ModuleInfo mod1, ModuleInfo mod2) {
super("Duplicate module files "+mod1.file+" and "+mod2.file+" found, both with main class "+mod1.mainClassname);
this.mod1=mod1;
this.mod2=mod2;
}
}
public List<Class<?>> getMainClasses() {
ArrayList<Class<?>> result=new ArrayList<Class<?>>();
for (ModuleInfo m: modules) {
if (m.mainClassname!=null) {
Class<?> c = getClass(m.mainClassname);
result.add(c);
System.out.println("Found "+c.getSimpleName()+"\tfrom file "+m.file+"\tversion "+m.version);
}
}
return result;
}
}