// Copyright (c) Corporation for National Research Initiatives // Copyright 2000 Samuele Pedroni package org.python.core; import java.util.Hashtable; import java.util.Vector; import java.util.Enumeration; import java.util.zip.ZipInputStream; import java.util.zip.ZipEntry; import java.io.*; import java.net.URL; import java.net.URLConnection; //import java.net.URLDecoder; import java.lang.reflect.Modifier; /** Abstract package manager that gathers info about statically known classes * from a set of jars. This info can be eventually cached. * Off-the-shelf this class offers a local file-system based cache impl. */ public abstract class CachedJarsPackageManager extends PackageManager { /** Message log method - hook. This default impl does nothing. * @param msg message text */ protected void message(String msg) { } /** Warning log method - hook. This default impl does nothing. * @param warn warning text */ protected void warning(String warn) { } /** Comment log method - hook. This default impl does nothing. * @param msg message text */ protected void comment(String msg) { } /** Debug log method - hook. This default impl does nothing. * @param msg message text */ protected void debug(String msg) { } /** Filter class/pkg by name helper method - hook. * The default impl. is used by {@link #addJarToPackages} in order * to filter out classes whose name contains '$' (e.g. inner classes,...). * Should be used or overriden by derived classes too. * Also to be used in {@link #doDir}. * @param name class/pkg name * @param pkg if true, name refers to a pkg * @return true if name must be filtered out */ protected boolean filterByName(String name,boolean pkg) { return name.indexOf('$') != -1; } /** Filter class by access perms helper method - hook. * The default impl. is used by {@link #addJarToPackages} in order * to filter out non-public classes. * Should be used or overriden by derived classes too. * Also to be used in {@link #doDir}. * Access perms can be read with {@link #checkAccess}. * @param name class name * @param acc class access permissions as int * @return true if name must be filtered out */ protected boolean filterByAccess(String name,int acc) { return (acc & Modifier.PUBLIC) != Modifier.PUBLIC; } private boolean indexModified; private Hashtable jarfiles; private static String vectorToString(Vector vec) { int n = vec.size(); StringBuffer ret = new StringBuffer(); for(int i=0; i 0) { classes += '@' + vectorToString(vec[1]); } zipPackages.put(key, classes); } return zipPackages; } /** Gathers classes info from jar specified by jarurl URL. * Eventually just using previously cached info. * Eventually updated info is not cached. * Persistent cache storage access goes through * inOpenCacheFile() and outCreateCacheFile(). */ public void addJarToPackages(java.net.URL jarurl) { addJarToPackages(jarurl,null,false); } /** Gathers classes info from jar specified by jarurl URL. * Eventually just using previously cached info. * Eventually updated info is (re-)cached if param cache is true. * Persistent cache storage access goes through * inOpenCacheFile() and outCreateCacheFile(). */ public void addJarToPackages(URL jarurl,boolean cache) { addJarToPackages(jarurl,null,cache); } /** Gathers classes info from jar specified by File jarfile. * Eventually just using previously cached info. * Eventually updated info is not cached. * Persistent cache storage access goes through * inOpenCacheFile() and outCreateCacheFile(). */ public void addJarToPackages(File jarfile) { addJarToPackages(null,jarfile,false); } /** Gathers classes info from jar specified by File jarfile. * Eventually just using previously cached info. * Eventually updated info is (re-)cached if param cache is true. * Persistent cache storage access goes through * inOpenCacheFile() and outCreateCacheFile(). */ public void addJarToPackages(File jarfile,boolean cache) { addJarToPackages(null,jarfile,cache); } private void addJarToPackages(URL jarurl,File jarfile,boolean cache) { try { boolean caching = jarfiles!=null; URLConnection jarconn = null; boolean localfile = true; if (jarfile == null) { jarconn = jarurl.openConnection(); // This is necessary because 'file:' url-connections // return always 0 through getLastModified (bug?). // And in order to handle localfiles (from urls too) // uniformly. if(jarconn.getURL().getProtocol().equals("file")) { // ??pending: need to use java2 URLDecoder.decode? // but under 1.1 this is absent and should be simulated. String jarfilename = jarurl.getFile(); jarfilename = jarfilename.replace('/',File.separatorChar); jarfile = new File(jarfilename); } else localfile = false; } if (localfile && !jarfile.exists()) return; Hashtable zipPackages = null; long mtime = 0; String jarcanon = null; JarXEntry entry = null; boolean brandNew = false; if(caching) { if(localfile) { mtime = jarfile.lastModified(); jarcanon = jarfile.getCanonicalPath(); } else { mtime = jarconn.getLastModified(); jarcanon = jarurl.toString(); } entry = (JarXEntry)jarfiles.get(jarcanon); if (entry == null && cache) { message("processing new jar, '"+ jarcanon+"'"); String jarname; if(localfile) { jarname = jarfile.getName(); } else { jarname = jarurl.getFile(); int slash = jarname.lastIndexOf('/'); if (slash != -1) jarname=jarname.substring(slash+1); } jarname=jarname.substring(0,jarname.length()-4); entry = new JarXEntry(jarname); jarfiles.put(jarcanon, entry); brandNew = true; } if (mtime != 0 && entry != null && entry.mtime == mtime) { zipPackages = readCacheFile(entry, jarcanon); } } if (zipPackages == null) { caching = caching && cache; if(caching) { indexModified = true; if (entry.mtime != 0) { message("processing modified jar, '"+ jarcanon+"'"); } entry.mtime = mtime; } InputStream jarin; if (jarconn == null) jarin = new BufferedInputStream( new FileInputStream(jarfile)); else jarin = jarconn.getInputStream(); zipPackages = getZipPackages(jarin); if (caching) writeCacheFile(entry, jarcanon, zipPackages, brandNew); } addPackages(zipPackages, jarcanon); } catch (IOException ioe) { // silently skip any bad directories warning("skipping bad jar, '" + (jarfile != null ? jarfile.toString() : jarurl.toString()) + "'"); } } private void addPackages(Hashtable zipPackages, String jarfile) { for (Enumeration e = zipPackages.keys() ; e.hasMoreElements() ;) { String pkg = (String)e.nextElement(); String classes = (String)zipPackages.get(pkg); int idx = classes.indexOf('@'); if (idx >= 0 && Options.respectJavaAccessibility) { classes = classes.substring(0, idx); } makeJavaPackage(pkg, classes, jarfile); } } // Read in cache file storing package info for a single .jar // Return null and delete this cachefile if it is invalid private Hashtable readCacheFile(JarXEntry entry,String jarcanon) { String cachefile = entry.cachefile; long mtime = entry.mtime; debug("reading cache, '"+jarcanon+"'"); try { DataInputStream istream = inOpenCacheFile(cachefile); String old_jarcanon = istream.readUTF(); long old_mtime = istream.readLong(); if ((!old_jarcanon.equals(jarcanon)) || (old_mtime != mtime)) { comment("invalid cache file: "+ cachefile+", "+jarcanon+":"+ old_jarcanon+", "+mtime+":"+old_mtime); deleteCacheFile(cachefile); return null; } Hashtable packs = new Hashtable(); try { while (true) { String packageName = istream.readUTF(); String classes = istream.readUTF(); packs.put(packageName, classes); } } catch (EOFException eof) { ; } istream.close(); return packs; } catch (IOException ioe) { // if (cachefile.exists()) cachefile.delete(); return null; } } // Write a cache file storing package info for a single .jar private void writeCacheFile(JarXEntry entry,String jarcanon, Hashtable zipPackages,boolean brandNew) { try { DataOutputStream ostream = outCreateCacheFile(entry, brandNew); ostream.writeUTF(jarcanon); ostream.writeLong(entry.mtime); comment("rewriting cachefile for '"+jarcanon+"'"); for (Enumeration e = zipPackages.keys() ; e.hasMoreElements() ;) { String packageName = (String)e.nextElement(); String classes = (String)zipPackages.get(packageName); ostream.writeUTF(packageName); ostream.writeUTF(classes); } ostream.close(); } catch (IOException ioe) { warning("can't write cache file for '"+jarcanon+"'"); } } /** Initializes cache. Eventually reads back cache index. * Index persistent storage is accessed through inOpenIndex(). */ protected void initCache() { indexModified = false; jarfiles = new Hashtable(); try { DataInputStream istream = inOpenIndex(); if (istream == null) return; try { while (true) { String jarcanon = istream.readUTF(); String cachefile = istream.readUTF(); long mtime = istream.readLong(); jarfiles.put(jarcanon, new JarXEntry(cachefile,mtime)); } } catch (EOFException eof) { ; } istream.close(); } catch (IOException ioe) { warning("invalid index file"); } } /** Write back cache index. * Index persistent storage is accessed through outOpenIndex(). */ public void saveCache() { if(jarfiles == null || !indexModified ) return; indexModified = false; comment("writing modified index file"); try { DataOutputStream ostream = outOpenIndex(); for (Enumeration e = jarfiles.keys(); e.hasMoreElements();) { String jarcanon = (String)e.nextElement(); JarXEntry entry = (JarXEntry)jarfiles.get(jarcanon); ostream.writeUTF(jarcanon); ostream.writeUTF(entry.cachefile); ostream.writeLong(entry.mtime); } ostream.close(); } catch (IOException ioe) { warning("can't write index file"); } } // hooks for changing cache storage /** To pass a cachefile id by ref. And for internal use. * See outCreateCacheFile */ public static class JarXEntry extends Object { /** cachefile id */ public String cachefile; public long mtime; public JarXEntry(String cachefile) { this.cachefile = cachefile; } public JarXEntry(String cachefile,long mtime) { this.cachefile = cachefile; this.mtime = mtime; } } /** Open cache index for reading from persistent storage - hook. * Must Return null if this is absent. * This default impl is part of the off-the-shelf local * file-system cache impl. * Can be overriden. */ protected DataInputStream inOpenIndex() throws IOException { File indexFile = new File(cachedir, "packages.idx"); if (!indexFile.exists()) return null; DataInputStream istream = new DataInputStream( new BufferedInputStream(new FileInputStream(indexFile))); return istream; } /** Open cache index for writing back to persistent storage - hook. * This default impl is part of the off-the-shelf local * file-system cache impl. * Can be overriden. */ protected DataOutputStream outOpenIndex() throws IOException { File indexFile = new File(cachedir, "packages.idx"); return new DataOutputStream( new BufferedOutputStream(new FileOutputStream(indexFile))); } /** Open cache file for reading from persistent storage - hook. * This default impl is part of the off-the-shelf local * file-system cache impl. * Can be overriden. */ protected DataInputStream inOpenCacheFile(String cachefile) throws IOException { return new DataInputStream( new BufferedInputStream( new FileInputStream(cachefile))); } /** Delete (invalidated) cache file from persistent storage - hook. * This default impl is part of the off-the-shelf local * file-system cache impl. * Can be overriden. */ protected void deleteCacheFile(String cachefile) { new File(cachefile).delete(); } /** * Create/open cache file for rewriting back to persistent storage - hook. * If create is false, cache file is supposed to exist and must be opened * for rewriting, entry.cachefile is a valid cachefile id. * If create is true, cache file must be created. entry.cachefile is a * flat jarname to be used to produce a valid cachefile id (to be put * back in entry.cachefile on exit). * This default impl is part of the off-the-shelf local file-system * cache impl. * Can be overriden. */ protected DataOutputStream outCreateCacheFile(JarXEntry entry, boolean create) throws IOException { File cachefile = null; if(create) { int index = 1; String suffix = ""; String jarname = entry.cachefile; while (true) { cachefile = new File(cachedir, jarname+suffix+".pkc"); //System.err.println("try cachefile: "+cachefile); if (!cachefile.exists()) break; suffix = "$"+index; index += 1; } entry.cachefile = cachefile.getCanonicalPath(); } else cachefile = new File(entry.cachefile); return new DataOutputStream( new BufferedOutputStream( new FileOutputStream(cachefile))); } // for default cache (local fs based) impl private File cachedir; /** Initialize off-the-shelf (default) local file-system cache impl. * Must be called before {@link #initCache}. * cachedir is the cache repository directory, this is eventually created. * Returns true if dir works. */ protected boolean useCacheDir(File cachedir) { if(cachedir == null) return false; if (!cachedir.isDirectory() && cachedir.mkdirs() == false) { warning("can't create package cache dir, '"+cachedir+"'"); return false; } this.cachedir = cachedir; return true; } }