package jadex.commons; import jadex.commons.collection.LRU; import java.io.IOException; import java.util.LinkedHashMap; import java.util.Map; /** * Loader for managing models, loaded from disc and kept in cache. */ public abstract class AbstractModelLoader { //-------- attributes -------- /** The supported file extensions (if any). */ protected String[] extensions; /** The model cache (filename -> loaded model). */ protected LRU modelcache; /** The registered models (filename -> loaded model). */ protected Map registered; //-------- constructors -------- /** * Create a model loader. * @param extensions The supported file extensions by order of importance. */ public AbstractModelLoader(String[] extensions) { this(extensions, 200); } /** * Create a model loader. * @param extensions The supported file extensions by order of importance. */ public AbstractModelLoader(String[] extensions, int cachesize) { this.extensions = extensions; this.modelcache = new LRU(cachesize); this.registered = new LinkedHashMap(); } //-------- helper methods -------- /** * Load a model. * @param name The original name (i.e. not filename). * @param info The resource info. */ protected abstract ICacheableModel doLoadModel(String name, ResourceInfo info, ClassLoader classloader) throws Exception; /** * Find the file for a given name using any supported extension. * @param name The filename or logical name (resolved via imports and extensions). * @param imports The imports, if any. * @return The resource info identifying the file. * @throws Exception when the file could not be found. */ protected ResourceInfo getResourceInfo(String name, String[] imports, ClassLoader classloader) throws Exception { ResourceInfo ret; if(registered.containsKey(name)) { // Hack!!! ignore file handling for registered models. ICacheableModel model = (ICacheableModel)registered.get(name); ret = new ResourceInfo(name, null, model.getLastModified()); } else { ret = getResourceInfo0(name, imports, classloader); if(ret==null || ret.getInputStream()==null) throw new IOException("File "+name+" not found in imports.");//: "+SUtil.arrayToString(imports)); } return ret; } /** * Find the file for a given name using any supported extension. * @param name The filename or logical name (resolved via imports and extensions). * @param imports The imports, if any. * @return The resource info identifying the file or null. */ protected ResourceInfo getResourceInfo0(String name, String[] imports, ClassLoader classloader) { // Try to find directly as absolute path. ResourceInfo ret = SUtil.getResourceInfo0(name, classloader); for(int ex=0; (ret==null || ret.getInputStream()==null) && ex<extensions.length; ex++) { // Fully qualified package name? Can also be full package name with empty package ;-) String resstr = SUtil.replace(name, ".", "/") + extensions[ex]; ret = SUtil.getResourceInfo0(resstr, classloader); // Try to find in imports. for(int i=0; (ret==null || ret.getInputStream()==null) && imports!=null && i<imports.length; i++) { // Package import if(imports[i].endsWith(".*")) { resstr = SUtil.replace(imports[i].substring(0, imports[i].length()-1), ".", "/") + name + extensions[ex]; ret = SUtil.getResourceInfo0(resstr, classloader); } // Direct import else if(imports[i].endsWith(name)) { resstr = SUtil.replace(imports[i], ".", "/") + extensions[ex]; ret = SUtil.getResourceInfo0(resstr, classloader); } } } return ret; } /** * Find the file for a given name. * @param name The filename or logical name (resolved via imports and extension). * @param extension The required extension. * @param imports The imports, if any. * @return The resource info identifying the file. */ protected ResourceInfo getResourceInfo(String name, String extension, String[] imports, ClassLoader classloader) throws Exception { ResourceInfo ret; if(registered.containsKey(name)) { // Hack!!! ignore file handling for registered models. ICacheableModel model = (ICacheableModel)registered.get(name); ret = new ResourceInfo(name, null, model.getLastModified()); } else { // Try to find directly as absolute path. String resstr = name; ret = SUtil.getResourceInfo0(resstr, classloader); if(ret!=null && !ret.getFilename().endsWith(extension)) ret = null; if(name.endsWith(extension)) name = name.substring(0, name.length()-extension.length()); if(ret==null || ret.getInputStream()==null) { // Fully qualified package name? Can also be full package name with empty package ;-) resstr = SUtil.replace(name, ".", "/") + extension; ret = SUtil.getResourceInfo0(resstr, classloader); // Try to find in imports. for(int i=0; (ret==null || ret.getInputStream()==null) && imports!=null && i<imports.length; i++) { // Package import if(imports[i].endsWith(".*")) { resstr = SUtil.replace(imports[i].substring(0, imports[i].length()-1), ".", "/") + name + extension; ret = SUtil.getResourceInfo0(resstr, classloader); } // Direct import else if(imports[i].endsWith(name)) { resstr = SUtil.replace(imports[i], ".", "/") + extension; ret = SUtil.getResourceInfo0(resstr, classloader); } } } if(ret==null || ret.getInputStream()==null) throw new IOException("File "+name+" not found in imports");//: "+SUtil.arrayToString(imports)); } return ret; } // todo: synchronize modelcache! /** * Set the class loader. * @param classloader The class loader. * / public synchronized void setClassLoader(ClassLoader classloader) { // System.out.println("Classloader set: "+this+" "+classloader); this.classloader = classloader; modelcache.clear(); }*/ //-------- methods -------- /** * Load a model. * @param name The name of the model (file name or logical name). * @param imports The imports to use when resolving logical names. */ public synchronized ICacheableModel loadModel(String name, String[] imports, ClassLoader classloader) throws Exception { return loadModel(name, null, imports, classloader); } /** * Load a model with a required extension. * @param name The name of the model (file name or logical name). * @param extension The specific extension to look for. * @param imports The imports to use when resolving logical names. */ public synchronized ICacheableModel loadModel(String name, String extension, String[] imports, ClassLoader classloader) throws Exception { // System.out.println("filename: "+name); // Lookup cache by name/extension/imports ICacheableModel cached = null; Object[] keys = imports!=null? new Object[imports.length+3]: new Object[3]; keys[0] = name; keys[1] = extension; keys[2] = classloader; if(imports!=null) System.arraycopy(imports, 0, keys, 3, imports.length); Tuple keytuple = new Tuple(keys); ResourceInfo info = null; // synchronized(modelcache) // { cached = getCachedModel(keytuple); // If model is in cache, check at most every second if file on disc is newer. if(cached!=null && cached.getLastChecked()+1000<System.currentTimeMillis()) { info = extension!=null ? getResourceInfo(name, extension, imports, classloader) : getResourceInfo(name, imports, classloader); if(cached.getLastModified()<info.getLastModified()) { cached = null; } else { cached.setLastChecked(System.currentTimeMillis()); info.cleanup(); } } // } if(cached==null && info==null) { // Lookup cache by resolved filename. // synchronized(modelcache) // { info = extension!=null ? getResourceInfo(name, extension, imports, classloader) : getResourceInfo(name, imports, classloader); cached = getCachedModel(info.getFilename()); if(cached!=null) { if(cached.getLastModified()<info.getLastModified()) { cached = null; } else { cached.setLastChecked(System.currentTimeMillis()); } // Associate cached model to new key (name/extension/imports). modelcache.put(keytuple, cached); } // } } // Not found: load from disc and store in cache. if(cached==null) { try { cached = doLoadModel(name, info, classloader); // Store by filename also, to avoid reloading with different imports. modelcache.put(info.getFilename(), cached); // Associate cached model to new key (name/extension/imports). modelcache.put(keytuple, cached); } finally { info.cleanup(); } } return cached; } /** * Test, if a resource is loadable (in principle). * Tests if the resource is a file that matches the supported file extensions * or if the resource is a logical name, tests if a corresponding file exist. * The file is not actually loaded, i.e. the content of the file is not checked. * / public boolean isLoadable(String name, String[] imports) { boolean loadable = false; ResourceInfo rinfo = getResourceInfo0(name, imports); if(rinfo!=null) { String filename = rinfo.getFilename(); for(int i=0; !loadable && i<extensions.length; i++) { loadable = filename.endsWith(extensions[i]); } } return loadable; }*/ /** * * / protected String getFilenameExtension(String filename) { String ret = null; for(int i=1; i<extensions.length; i++) // skip i=0 for no extension. { if(filename.endsWith(extensions[i])) ret = extensions[i]; } if(ret==null) throw new RuntimeException("Unknown extension: "+filename); return ret; }*/ /** * Register a model. */ public void registerModel(Object key, ICacheableModel model) { registered.put(key, model); } /** * Deregister a model. */ public void deregisterModel(Object key) { registered.remove(key); } /** * Clears the model cache. */ public void clearModelCache() { modelcache.clear(); } /** * Get a model from cache (if any). */ protected ICacheableModel getCachedModel(Object key) { ICacheableModel ret = null; if(modelcache.containsKey(key)) { ret = (ICacheableModel)modelcache.get(key); } else if(registered.containsKey(key)) { ret = (ICacheableModel)registered.get(key); } return ret; } }