package rescuecore2.misc.java;
import static rescuecore2.misc.java.JavaTools.instantiateFactory;
import rescuecore2.config.Config;
import rescuecore2.registry.MessageFactory;
import rescuecore2.registry.EntityFactory;
import rescuecore2.registry.PropertyFactory;
import rescuecore2.registry.Registry;
import rescuecore2.Constants;
import rescuecore2.log.Logger;
import java.util.List;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Set;
import java.util.HashSet;
import java.util.Collection;
import java.util.Collections;
import java.util.jar.JarFile;
import java.util.jar.JarEntry;
import java.util.jar.Manifest;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
/**
A utility class for processing loadable types from jar files.
*/
public class LoadableTypeProcessor {
private List<LoadableTypeCallback> callbacks;
private Set<LoadableType> types;
private boolean deep;
private String dir;
private Set<String> ignore;
/**
Construct a LoadableTypeProcessor that will perform a deep inspection.
@param config The system configuration.
*/
public LoadableTypeProcessor(Config config) {
callbacks = new ArrayList<LoadableTypeCallback>();
types = new HashSet<LoadableType>();
deep = config.getBooleanValue(Constants.DEEP_JAR_INSPECTION_KEY, Constants.DEFAULT_DEEP_JAR_INSPECTION);
dir = config.getValue(Constants.JAR_DIR_KEY, Constants.DEFAULT_JAR_DIR);
ignore = new HashSet<String>();
ignore.addAll(config.getArrayValue(Constants.IGNORE_JARS_KEY, Constants.DEFAULT_IGNORE_JARS));
}
/**
Add the message, property and entity factory register callbacks.
@param registry The Registry to register factory classes with.
*/
public void addFactoryRegisterCallbacks(Registry registry) {
addCallback(new MessageFactoryRegisterCallback(registry));
addCallback(new EntityFactoryRegisterCallback(registry));
addCallback(new PropertyFactoryRegisterCallback(registry));
}
/**
Add a LoadableTypeCallback function.
@param callback The callback to add.
*/
public void addCallback(LoadableTypeCallback callback) {
callbacks.add(callback);
types.addAll(callback.getTypes());
}
/**
Add a config updating callback. This will append class names to a Config entry when a particular LoadableType returns an acceptable class.
@param type The type to look for.
@param config The config to update.
@param configKey The key to update.
*/
public void addConfigUpdater(LoadableType type, Config config, String configKey) {
addCallback(new ConfigCallback(type, config, configKey));
}
/**
Set whether to do a "deep" inspection or just inspect the manifest. If true then all entries will be tested to see if they match the target regex and class.
@param newDeep Whether to do a deep inspection or not.
*/
public void setDeepInspection(boolean newDeep) {
this.deep = newDeep;
}
/**
Set the name of the directory to process.
@param name The name of the directory.
*/
public void setDirectory(String name) {
dir = name;
}
/**
Process all jars in a directory.
@throws IOException If there is a problem reading the jar files.
*/
public void process() throws IOException {
File baseDir = new File(dir);
Logger.info("Processing jar directory: " + baseDir.getAbsolutePath());
File[] jarFiles = baseDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dirName, String name) {
return name.endsWith(".jar");
}
});
if (jarFiles == null) {
return;
}
for (File next : jarFiles) {
JarFile jar = new JarFile(next);
processJarFile(jar);
}
}
/**
Inspect an individual jar file for loadable types.
@param jar The jar file to inspect.
@throws IOException If there is a problem reading the jar file.
*/
public void processJarFile(JarFile jar) throws IOException {
String name = jar.getName();
String tail = name.substring(name.lastIndexOf("/") + 1);
if (ignore.contains(tail)) {
return;
}
Logger.info("Processing " + jar.getName());
Manifest mf = jar.getManifest();
if (mf != null) {
Logger.debug("Inspecting manifest...");
for (LoadableType type : types) {
for (String next : type.processManifest(mf)) {
fireCallback(type, next);
}
}
}
if (deep) {
// Look for well-named classes
Logger.debug("Looking for likely class names...");
for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements();) {
JarEntry next = e.nextElement();
for (LoadableType type : types) {
String s = type.processJarEntry(next);
if (s != null) {
fireCallback(type, s);
}
}
}
}
}
private void fireCallback(LoadableType type, String classname) {
for (LoadableTypeCallback next : callbacks) {
if (next.getTypes().contains(type)) {
next.classFound(type, classname);
}
}
}
private static class ConfigCallback implements LoadableTypeCallback {
private LoadableType type;
private Config config;
private String key;
public ConfigCallback(LoadableType type, Config config, String key) {
this.type = type;
this.config = config;
this.key = key;
}
@Override
public void classFound(LoadableType otherType, String className) {
Logger.info("Adding " + className + " to " + key);
if (config.isDefined(key)) {
List<String> existing = config.getArrayValue(key);
if (!existing.contains(className)) {
config.appendValue(key, className);
}
}
else {
config.setValue(key, className);
}
}
@Override
public Collection<LoadableType> getTypes() {
return Collections.singleton(type);
}
}
/**
A LoadableTypeCallback that will registry MessageFactory implementations.
*/
public static final class MessageFactoryRegisterCallback implements LoadableTypeCallback {
private Registry registry;
private MessageFactoryRegisterCallback(Registry registry) {
this.registry = registry;
}
@Override
public void classFound(LoadableType type, String className) {
MessageFactory factory = instantiateFactory(className, MessageFactory.class);
if (factory != null) {
registry.registerMessageFactory(factory);
Logger.info("Registered message factory '" + className + "' with registry " + registry.getName());
}
}
@Override
public Collection<LoadableType> getTypes() {
return Collections.singleton(LoadableType.MESSAGE_FACTORY);
}
}
/**
A LoadableTypeCallback that will registry EntityFactory implementations.
*/
public static final class EntityFactoryRegisterCallback implements LoadableTypeCallback {
private Registry registry;
private EntityFactoryRegisterCallback(Registry registry) {
this.registry = registry;
}
@Override
public void classFound(LoadableType type, String className) {
EntityFactory factory = instantiateFactory(className, EntityFactory.class);
if (factory != null) {
registry.registerEntityFactory(factory);
Logger.info("Registered entity factory '" + className + "' with registry " + registry.getName());
}
}
@Override
public Collection<LoadableType> getTypes() {
return Collections.singleton(LoadableType.ENTITY_FACTORY);
}
}
/**
A LoadableTypeCallback that will registry PropertyFactory implementations.
*/
public static final class PropertyFactoryRegisterCallback implements LoadableTypeCallback {
private Registry registry;
private PropertyFactoryRegisterCallback(Registry registry) {
this.registry = registry;
}
@Override
public void classFound(LoadableType type, String className) {
PropertyFactory factory = instantiateFactory(className, PropertyFactory.class);
if (factory != null) {
registry.registerPropertyFactory(factory);
Logger.info("Registered property factory '" + className + "' with registry " + registry.getName());
}
}
@Override
public Collection<LoadableType> getTypes() {
return Collections.singleton(LoadableType.PROPERTY_FACTORY);
}
}
}