// Copyright 2001 Finn Bock package org.python.util; import java.lang.reflect.*; import java.io.*; import java.util.*; import java.util.zip.*; public class jythonjar { static boolean useClasspath = false; static String classpath; public static void main(String[] args) throws Exception { int index=0; while (index < args.length && args[index].startsWith("-")) { String arg = args[index]; if (arg.equals("-usecp")) { useClasspath = true; } else if (arg.equals("-cp") || arg.equals("-classpath")) { classpath = args[++index];; } else { String opt = args[index]; if (opt.startsWith("--")) opt = opt.substring(2); else if (opt.startsWith("-")) opt = opt.substring(1); System.err.println("jythonjar: illegal option -- " + opt); return; } index += 1; } int n = args.length-index; if (n != 1) { System.err.println("jythonjar: filename missing"); return; } JarIndexer indexer = new JarIndexer(); indexer.setMakeParentPackages(true); File in = new File(args[index]); FileInputStream instream = new FileInputStream(in); indexer.getZipPackages(instream); instream.close(); if (useClasspath) { String cp = classpath; if (cp == null) cp = System.getProperty("java.class.path"); StringTokenizer st = new StringTokenizer(cp, File.pathSeparator); while (st.hasMoreTokens()) { String path = st.nextToken(); File cpEntry = new File(path); if (cpEntry.isFile() && cpEntry.canRead()) { System.out.println("Scanning: " + cpEntry); FileInputStream stream = new FileInputStream(cpEntry); indexer.getZipPackages(stream); stream.close(); } } } File tmp = File.createTempFile("jyjar", ".jar", new File(".")); FileOutputStream outstream = new FileOutputStream(tmp); File backupName = new File(in.getName() + ".old"); instream = new FileInputStream(in); copy(indexer.getPackages(), instream, outstream); instream.close(); outstream.close(); if (!in.renameTo(backupName)) { System.err.println("jythonjar: failed to rename input file to " + backupName); return; } if (!tmp.renameTo(in)) { System.err.println("jythonjar: failed to rename output file"); return; } } public static void copy(HashMap zipPackages, InputStream jarin, OutputStream jarout) throws IOException { ZipInputStream zip = new ZipInputStream(jarin); ZipOutputStream zipout = new ZipOutputStream(jarout); byte[] buff = new byte[4096]; int len = 0; // Copy the existing zip entries. ZipEntry entry; while ((entry = zip.getNextEntry()) != null) { if (entry.getName().startsWith("META-INF/JYTHON-PACKAGE-")) continue; zipout.putNextEntry(entry); while ((len = zip.read(buff)) > 0) { zipout.write(buff, 0, len); } zip.closeEntry(); zipout.closeEntry(); } // Write the index for (Iterator e = zipPackages.keySet().iterator() ; e.hasNext() ;) { Object key = e.next(); Classes classes = (Classes) zipPackages.get(key); byte[] bytes = classes.toUTF8(); entry = new ZipEntry("META-INF/JYTHON-PACKAGE-" + key); entry.setSize(bytes.length); entry.setCompressedSize(bytes.length); entry.setMethod(ZipEntry.STORED); CRC32 crc = new CRC32(); crc.update(bytes); entry.setCrc(crc.getValue()); zipout.putNextEntry(entry); zipout.write(bytes); zipout.closeEntry(); } zipout.close(); } } class JarIndexer { HashMap zipPackages; boolean makeParent = false; public JarIndexer() { zipPackages = new HashMap(); } public void setMakeParentPackages(boolean makeParent) { this.makeParent = makeParent; } public HashMap getPackages() { return zipPackages; } // Extract all of the packages in a single jarfile public void getZipPackages(InputStream jarin) throws IOException { ZipInputStream zip = new ZipInputStream(jarin); ZipEntry entry; while ((entry = zip.getNextEntry()) != null) { addZipEntry(entry, zip); zip.closeEntry(); } zip.close(); } // Add a single class from zipFile to zipPackages // Only add valid classes private void addZipEntry(ZipEntry entry, InputStream zip) throws IOException { String name = entry.getName(); //System.err.println("entry: "+name); if (!name.endsWith(".class")) return; char sep = '/'; int breakPoint = name.lastIndexOf(sep); if (breakPoint == -1) { breakPoint = name.lastIndexOf('\\'); sep = '\\'; } String packageName; if (breakPoint == -1) { packageName = ""; } else { packageName = name.substring(0,breakPoint).replace(sep, '.'); } String className = name.substring(breakPoint+1, name.length()-6); if (filterByName(className,false)) return; Classes classes = addPackage(packageName); int access = checkAccess(zip); if ((access != -1) && !filterByAccess(name,access)) { classes.addPublic(className); } else { classes.addPrivate(className); } } private Classes addPackage(String packageName) { Classes classes = (Classes) zipPackages.get(packageName); if (classes == null) { classes = new Classes(); zipPackages.put(packageName, classes); int idx = packageName.lastIndexOf('.'); if (idx > 0 && makeParent) addPackage(packageName.substring(0, idx)); } return classes; } private static final int MAXSKIP = 512; /** Check that a given stream is a valid Java .class file. * And return its access permissions as an int. */ static protected int checkAccess(java.io.InputStream cstream) throws java.io.IOException { java.io.DataInputStream istream=new java.io.DataInputStream(cstream); int magic = istream.readInt(); int minor = istream.readShort(); int major = istream.readShort(); if (magic != 0xcafebabe) return -1; // Check versions??? //System.out.println("magic: "+magic+", "+major+", "+minor); int nconstants = istream.readShort(); for(int i=1; i MAXSKIP) { // workaround to java1.1 bug istream.skipBytes(MAXSKIP); slength -= MAXSKIP; } istream.skipBytes(slength); break; default: //System.err.println("unexpected cid: "+cid+", "+i+", "+ // nconstants); //for (int j=0; j<10; j++) // System.err.print(", "+istream.readByte()); //System.err.println(); return -1; } } return istream.readShort(); } /** 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; } } class Classes { private Vector publics = new Vector(); private Vector privates = new Vector(); public void addPublic(String className) { publics.addElement(className); } public void addPrivate(String className) { privates.addElement(className); } private void vectorToString(StringBuffer ret, Vector v) { int n = v.size(); for (int i = 0; i < n; i++) { ret.append((String) v.elementAt(i)); if (i 0) { ret.append('@'); vectorToString(ret, privates); } return ret.toString(); } public byte[] toUTF8() throws IOException { ByteArrayOutputStream bstream = new ByteArrayOutputStream(); new DataOutputStream(bstream).writeUTF(toString()); return bstream.toByteArray(); } }