001/*
002 *  $Rev: $:     Revision of last commit
003 *  $Date: $:    Date of last commit
004 *  $URL: $
005 *
006 *  This program is free software; you can redistribute it and/or modify
007 *  it under the terms of the GNU General Public License as published by
008 *  the Free Software Foundation; either version 3of the License, or
009 *  (at your option) any later version.
010 *
011 *  This program is distributed in the hope that it will be useful,
012 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
013 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 *  GNU General Public License for more details.
015 *
016 *  You should have received a copy of the GNU General Public License
017 *  along with this program; If not, see <http://www.gnu.org/licenses/>.
018*/
019
020/* $Log: ZipLock.java,v $
021 * Revision 1.10  2000/11/25  04:26:56  stuart
022 * Look inside ZIP/JAR when listing classes in a package
023 *
024 * Revision 1.9  2000/06/06  19:05:42  stuart
025 * resource name bug
026 *
027 * Revision 1.8  1999/08/24  02:06:19  stuart
028 * Expand default exclude list
029 *
030 * Revision 1.7  1998/11/17  02:20:40  stuart
031 * use imports instead of qualified names
032 *
033 * Revision 1.6  1998/11/11  19:29:01  stuart
034 * rename to ZipLock
035 * remove indents on file list
036 *
037 * Revision 1.5  1998/11/10  03:59:02  stuart
038 * Not fully reading zip entries!
039 *
040 * Revision 1.4  1998/11/10  02:28:50  stuart
041 * improve documentation
042 * improve HandleTable
043 *
044 * Revision 1.3  1998/11/05  20:21:06  stuart
045 * Support non-class resources
046 *
047 * Revision 1.2  1998/11/04  05:01:59  stuart
048 * package list, performance improved, use pathsep and filesep properties
049 *
050 *
051 * Enhanced by Stuart D. Gathman from an original progam named RollCall.
052 * Copyright (c) 1998 Business Management Systems, Inc.
053 *
054 * Original Copyright (c) 1998 Karl Moss. All Rights Reserved.
055 *
056 * You may study, use, modify, and distribute this software for any
057 * purpose provided that this copyright notice appears in all copies.
058 *
059 * This software is provided WITHOUT WARRANTY either expressed or
060 * implied.
061 *
062 * @original_author  Karl Moss
063 * @original_date    29Jan98
064 *
065*/
066
067package ca.bc.webarts.tools;
068
069import java.io.File;
070import java.io.InputStream;
071import java.io.ByteArrayInputStream;
072import java.io.FileInputStream;
073import java.io.FileOutputStream;
074import java.io.DataInputStream;
075import java.io.IOException;
076import java.io.FilenameFilter;
077import java.util.StringTokenizer;
078import java.util.Enumeration;
079import java.util.Hashtable;
080import java.util.Vector;
081import java.util.zip.ZipOutputStream;
082import java.util.zip.ZipFile;
083import java.util.zip.ZipEntry;
084import java.util.zip.ZipException;
085
086/**
087 * This is a utility class for examining a list of class names for
088 * all of their dependencies.
089 *
090 * <p>ZipLock will read each class file and search the internal
091 * structures for all references to outside classes and resources. The checks
092 * are recursive, so all classes will be examined. A list of
093 * dependencies will be returned.
094 *
095 * <p>None of the java.* classes will be examined.  Additional system
096 * packages may be excluded with <code>setExcludes()</code>.
097 *
098 * <p>In addition to classes, we look for other resources loaded via
099 * <code>Class.getResource()</code> or
100 * <code>Class.getResourceAsStream()</code>.  If a class calls these
101 * methods, then every String constant in the class is checked to see
102 * if a file by that name exists on the CLASSPATH in the same directory
103 * as the class - in other words where <code>getResource</code> would
104 * find it.  This heuristic only works if your resource names appear
105 * as String constants - which seems to be the case in my practice so far.
106 *
107 * <p>We can optionally write all the classes and resources
108 * found to a zip or jar, or the list of files can be retrieved
109 * with <code>getDependencies()</code>.
110 *
111 * @author Stuart D. Gathman
112 * Copyright (C) 1998 Business Management Systems, Inc.
113 * <p>Original version Copyright (c) 1998 Karl Moss. All Rights Reserved.
114 */
115
116public class ZipLock
117{
118  private static final String[] default_exclude =  { "java/", "sun/", "javax/" };
119  private String[] m_excludePackageList = default_exclude;
120  private final Hashtable m_dep = new Hashtable();
121  private int m_level = 0;
122  private ZipOutputStream m_archiveStream;
123  private static final String filesep = System.getProperty("file.separator");
124  private static final String[] classPath = getClassPath();
125  private final Vector rootSet = new Vector();
126  private static final Hashtable ziptbl = new Hashtable();  // cache jars
127
128  // reuse these data structures for getClassResources()
129  private final IntList classInfo = new IntList();  // which names are classes
130  private final IntList stringTbl = new IntList();  // which entries are Strings
131  private final Vector methInfo = new Vector();
132
133
134  private static final FilenameFilter classFilter = new FilenameFilter()
135  {
136    public boolean accept(File dir, String name)
137    {
138      return name.endsWith(".class");
139    }
140  };
141
142
143  /** Return the system class path as an array of strings. */
144  public static String[] getClassPath()
145  {
146    String classPath = System.getProperty("java.class.path");
147    return getClassPath(classPath);
148  }
149
150
151  /** Convert a class path to an array of strings. A class path is
152   * a list of path names separated by the character in the
153   * <code>path.separator</code> system property.
154   */
155  public static String[] getClassPath(String path)
156  {
157    String pathsep = System.getProperty("path.separator");
158    StringTokenizer tok = new StringTokenizer(path, pathsep);
159    String[] s = new String[tok.countTokens()];
160    for (int i = 0; tok.hasMoreTokens(); ++i)
161    {
162      s[i] = tok.nextToken();
163    }
164
165    return s;
166  }
167
168
169  /**
170   * <p>Sets the list of package names to exclude. If the package
171   * of the class starts with any of the names given, the class
172   * will be excluded. An example would be "foo.package".
173   *
174   * @param excludeList An array of package names to exclude.
175   */
176  public void setExcludes(String e[])
177  {
178    if (e == null)
179    {
180      m_excludePackageList = default_exclude;
181      return;
182    }
183    m_excludePackageList = new String[ e.length + default_exclude.length];
184    for (int i = 0; i < e.length; ++i)
185    {
186      m_excludePackageList[i] = e[i].replace('.', '/');
187    }
188
189    System.arraycopy(default_exclude,0, m_excludePackageList, e.length, default_exclude.length);
190  }
191
192
193  /**
194   * <p>Start the roll call. After the class list and package exclude
195   * list have been set, this method will perform the class
196   * examination. Once complete, call getDependencies() to get a
197   * full list of class dependencies. If an archive file was
198   * specified, the archive file will contain all of the dependencies.
199   *
200   * @exception Exception An error occurred while processing
201   * @see #getDependencies
202   * @param m_archive the output archive or null
203   */
204  public void start(String m_archive) throws Exception
205  {
206
207    // Attempt to create the archive if one was given
208
209    if (m_archive != null)
210    {
211      System.err.println("Creating archive " + m_archive);
212      File f = new File(m_archive);
213      FileOutputStream fo = new FileOutputStream(f);
214
215      // A new file was created. Create our zip output stream
216
217      m_archiveStream = new ZipOutputStream(fo);
218    }
219
220    // Loop for each class given in the list
221
222    m_dep.clear();
223    String[] m_classList = getRootSet();
224
225    for (int i = 0; i < m_classList.length; i++)
226    {
227      String name = m_classList[i];
228      storeClass(name);
229    }
230
231    // Close the archive if necessary
232
233    if (m_archiveStream != null)
234    {
235      m_archiveStream.close();
236    }
237
238    if (m_archive != null)
239    {
240      System.err.println("\n" + m_archive + " created.");
241    }
242  }
243
244
245  /** Return the list of classes in the root set.
246   */
247  public synchronized String[] getRootSet()
248  {
249    int n = rootSet.size();
250    String[] s = new String[n];
251    rootSet.copyInto(s);
252    return s;
253  }
254
255
256  /** Clear the root set and the list of dependencies - leaving this
257   * object ready for reuse. */
258  public void clear()
259  {
260    m_dep.clear();
261    rootSet.removeAllElements();
262  }
263
264
265  /** Return the list of class and other resources with their dependencies.  */
266  public synchronized String[] getDependencies()
267  {
268    int n = m_dep.size();
269    String[] s = new String[n];
270    Enumeration k = m_dep.keys();
271    for (int i = 0; i < n; ++i)
272    {
273      s[i] = (String) k.nextElement();
274    }
275
276    return s;
277  }
278
279
280  private void storeClass(String className) throws Exception
281  {
282
283    // First, convert the given class name into a resource file name
284    String res = className.replace('.', '/') + ".class";
285
286    // ignore if already processed
287    if (m_dep.get(res) != null)
288    {
289      return;
290    }
291
292    // Read the class into a memory buffer
293
294    byte buf[] = openResource(res);
295
296    if (buf == null)
297    {
298      throw new Exception("Class " + className + " not found");
299    }
300
301    // Now process the class
302    processClass(res, buf);
303
304  }
305
306
307  /**
308   * Given a class name, open it and return a buffer with
309   * the contents. The class is loaded from
310   * the current CLASSPATH setting
311   */
312  protected static byte[] openResource(String name) throws Exception
313  {
314    byte buf[] = null;
315
316    // Get the defined classpath
317
318    // Walk through the classpath
319
320    for (int i = 0; i < classPath.length; ++i)
321    {
322      String element = classPath[i];
323
324      // We've got an element from the classpath. Look for
325      // the resource here
326
327      buf = openResource(name, element);
328
329      // Got it! Exit the loop
330
331      if (buf != null)
332      {
333        break;
334      }
335    }
336
337    return buf;
338  }
339
340
341  /**
342   * Given a resource name and path, open the resource and
343   * return a buffer with the contents. Returns null if
344   * not found
345   */
346  protected static byte[] openResource(String name, String path) throws Exception
347  {
348    byte buf[] = null;
349
350    // If the path is a zip or jar file, look inside for the
351    // resource
352
353    String lPath = path.toLowerCase();
354    if (lPath.endsWith(".zip") || lPath.endsWith(".jar"))
355    {
356
357      buf = openResourceFromJar(name, path);
358    }
359    else
360    {
361
362      // Not a zip or jar file. Look for the resource as
363      // a file
364
365      String fullName = path;
366
367      // Put in the directory separator if necessary
368
369      if (!path.endsWith("\\") && !path.endsWith("/"))
370      {
371        fullName += filesep;
372      }
373      fullName += name;
374
375      File f = new File(fullName);
376
377      // Check to make sure the file exists and it truely
378      // is a file
379
380      if (f.exists() && f.isFile())
381      {
382
383        // Create an input stream and read the file
384
385        FileInputStream fi = new FileInputStream(f);
386        long length = f.length();
387        buf = new byte[(int) length];
388        DataInputStream ds = new DataInputStream(fi);
389        ds.readFully(buf);
390        ds.close();
391        System.out.println("opening: "+fullName);
392      }
393      //else
394      //  System.out.println("    NotFound: exists="+f.exists() + "  isFile=" + f.isFile());
395    }
396
397    return buf;
398  }
399
400
401  protected static ZipFile findJar(String jarFile) throws IOException
402  {
403    ZipFile zip = (ZipFile) ziptbl.get(jarFile);
404    if (zip == null)
405    {
406      File f = new File(jarFile);
407
408      // Make sure the file exists before opening it
409      if (f.exists() && f.isFile())
410      {
411
412        // Open the zip file
413        zip = new ZipFile(f);
414        ziptbl.put(jarFile, zip);
415      }
416    }
417    return zip;
418  }
419
420
421  /** Given a resource name and jar file name, open the jar file
422   * and return a buffer containing the contents. Returns null
423   * if the jar file could not be found or the resource could
424   * not be found
425   */
426  protected static byte[] openResourceFromJar(String name, String jarFile) throws Exception
427  {
428    ZipFile zip = findJar(jarFile);
429    if (zip == null)
430    {
431      return null;
432    }
433
434    // Is the entry in the zip file?
435
436    ZipEntry entry = zip.getEntry(name);
437    if (entry == null)
438    {
439      return null;
440    }
441    // If found, read the corresponding buffer for the entry
442
443    InputStream in = zip.getInputStream(entry);
444    DataInputStream ds = new DataInputStream(in);
445
446    // Get the number of bytes available
447
448    int len = (int) entry.getSize();
449
450    // Read the contents of the class
451    byte[] buf = new byte[len];
452    ds.readFully(buf);
453    ds.close();
454    return buf;
455  }
456
457
458  /**
459   * Given a class or resource name and buffer containing the contents,
460   * process the raw bytes of the class file.
461   */
462  protected void processClass(String className, byte buf[]) throws Exception
463  {
464
465    log(className);
466
467    // Save the fact that we have processed this class
468
469    setClassProcessed(className);
470
471    // If we are creating an archive, add this class to the
472    // archive now
473
474    if (m_archiveStream != null)
475    {
476      addToArchive(className, buf);
477    }
478
479    // If not a class resource, we are done
480    if (!className.endsWith(".class"))
481    {
482      return;
483    }
484
485    String[] a = getClassResources(buf, className);
486
487    for (int i = 0; i < a.length; ++i)
488    {
489      String s = a[i];
490
491      // Make sure we don't process classes we've already seen
492      // and those on our exclude list
493      if (isClassProcessed(s))
494      {
495        continue;
496      }
497
498      // Process this resource
499      buf = openResource(s);
500
501      // Ignore if the class could not be found
502
503      if (buf == null)
504      {
505        continue;
506      }
507
508      // Keep track of how deep we are
509      m_level++;
510
511      // Process the resource
512      processClass(s, buf);
513
514      m_level--;
515    }
516  }
517
518
519  static class Pair
520  {
521    final int a, b;
522    Pair(int a, int b)
523    { this.a = a; this.b = b; }
524  }
525
526
527  static class HandleTable
528  {
529    private Object[] tbl;
530
531    public HandleTable(int n)
532    {
533      tbl = new Object[n];
534    }
535
536    public void put(int i, Object name)
537    {
538      tbl[i] = name;
539    }
540    public void put(int i, int val)
541    {
542      put(i, new Integer(val));
543    }
544    public void put(int i, int a, int b)
545    {
546      put(i, new Pair(a, b));
547    }
548    public String getString(int i)
549    {
550      if (i >= tbl.length)
551      {
552        return null;
553      }
554      return (String) tbl[i];
555    }
556    public int getInt(int i)
557    {
558      return ((Integer) tbl[i]).intValue();
559    }
560    public Pair getPair(int i)
561    {
562      return (Pair) tbl[i];
563    }
564  }
565
566
567  /** Examine a class file to see what resources it uses, especially other
568   * classes. */
569  private String[] getClassResources(byte[] buf, String className) throws Exception
570  {
571    // Create a DataInputStream using the buffer. This will
572    // make reading the buffer very easy
573
574    ByteArrayInputStream bais = new ByteArrayInputStream(buf);
575
576    DataInputStream in = new DataInputStream(bais);
577
578    // Read the magic number. It should be 0xCAFEBABE
579
580    int magic = in.readInt();
581    if (magic != 0xCAFEBABE)
582    {
583      throw new Exception("Invalid magic number in " + className);
584    }
585
586    // Validate the version numbers
587
588    short minor = in.readShort();
589    short major = in.readShort();
590    if ( false && (  (minor != 3) || (major != 45)))
591    {
592      // The VM specification defines 3 as the minor version
593      // and 45 as the major version for 1.1
594      throw new Exception("Invalid version number (minor="+minor+" major="+major+") in " + className);
595    }
596
597    // Get the number of items in the constant pool
598
599    short count = in.readShort();
600
601    // Track which CP entries are classes and String contants
602    classInfo.removeAll();
603    stringTbl.removeAll();
604
605    // Keep a list of method references
606    methInfo.removeAllElements();
607
608    // Initialize the constant pool handle table
609    HandleTable cp = new HandleTable(count);    // Constant Pool
610
611    // Now walk through the constant pool looking for entries
612    // we are interested in.  Others can be ignored, but we need
613    // to understand the format so they can be skipped.
614    readcp: for (int i = 1; i < count; i++)
615    {
616      // Read the tag
617      byte tag = in.readByte();
618      switch (tag)
619      {
620        case 7:          // CONSTANT_Class
621          // Save the constant pool index for the class name
622          short nameIndex = in.readShort();
623          classInfo.add(nameIndex);
624          cp.put(i, nameIndex);
625          break;
626        case 10:          // CONSTANT_Methodref
627          short clazz = in.readShort();          // class
628          short nt = in.readShort();          // name and type
629          methInfo.addElement(new Pair(clazz, nt));
630          break;
631        case 9:          // CONSTANT_Fieldref
632        case 11:          // CONSTANT_InterfaceMethodref
633          // Skip past the structure
634          in.skipBytes(4);
635          break;
636        case 8:          // CONSTANT_String
637          // Skip past the string index
638          short strIndex = in.readShort();
639          stringTbl.add(strIndex);
640          break;
641        case 3:          // CONSTANT_Integer
642        case 4:          // CONSTANT_Float
643          // Skip past the data
644          in.skipBytes(4);
645          break;
646        case 5:          // CONSTANT_Long
647        case 6:          // CONSTANT_Double
648          // Skip past the data
649          in.skipBytes(8);
650
651          // As dictated by the Java Virtual Machine specification,
652          // CONSTANT_Long and CONSTANT_Double consume two
653          // constant pool entries.
654          i++;
655
656          break;
657        case 12:          // CONSTANT_NameAndType
658          int name = in.readShort();
659          int sig = in.readShort();
660          cp.put(i, name, sig);
661          break;
662        case 1:          // CONSTANT_Utf8
663          String s = in.readUTF();
664          cp.put(i, s);
665          break;
666        default:
667          System.err.println("WARNING: Unknown constant tag (" + tag + "@" + i + " of " + count + ") in " + className);
668          break readcp;
669      }
670    }
671
672    // We're done with the buffer and input streams
673
674    in.close();
675
676    Vector v = new Vector();    // collect resources used by this class
677
678    // Walk through our vector of class name
679    // index values and get the actual class names
680
681    // Copy the actual class names so tables can get reused
682    int[] ia = classInfo.elements();
683    for (int i = 0; i < ia.length; i++)
684    {
685      int idx = ia[i];
686      String s = cp.getString(idx);
687      if (s == null)
688      {
689        continue;
690      }
691
692      // Look for arrays. Only process arrays of objects
693      if (s.startsWith("["))
694      {
695        // Strip off all of the array indicators
696        while (s.startsWith("["))
697        {
698          s = s.substring(1);
699        }
700
701        // Only use the array if it is an object. If it is,
702        // the next character will be an 'L'
703        if (!s.startsWith("L"))
704        {
705          continue;
706        }
707
708        // Strip off the leading 'L' and trailing ';'
709        s = s.substring(1, s.length() - 1);
710      }
711      v.addElement(s + ".class");
712    }
713
714    // examine methods used for calls to getResource*()
715    boolean resourceUsed = false;
716    Pair[] p = new Pair[methInfo.size()];
717    methInfo.copyInto(p);
718    for (int i = 0; i < p.length; ++i)
719    {
720      try
721      {
722        String clazz = cp.getString(cp.getInt(p[i].a));
723        if ("java/lang/Class".equals(clazz))
724        {
725          Pair nt = cp.getPair(p[i].b);
726          String name = cp.getString(nt.a);
727          if (name.startsWith("getResource"))
728          {
729            resourceUsed = true;
730            System.err.println("getResource used in " + className);
731            break;
732          }
733        }
734      }
735      catch (IndexOutOfBoundsException x)
736      { }
737    }
738
739    if (resourceUsed)
740    {
741      /* string constants might be resource file names  Those that aren't
742      will get ignored when the resulting path is not found. */
743      int pos = className.lastIndexOf('/');
744      String res = className.substring(0, pos + 1);
745      ia = stringTbl.elements();
746      for (int i = 0; i < ia.length; ++i)
747      {
748        int idx = ia[i];
749        String s = res + cp.getString(ia[i]);
750        v.addElement(s);
751      }
752    }
753
754    String[] a = new String[v.size()];
755    v.copyInto(a);
756    return a;
757  }
758
759
760  /** Determine if the given class is in our list or is part
761   * of a system package such as java.* or a package specified
762   * with <coded>setExcludes()</code>.
763   */
764  public boolean isClassProcessed(String name)
765  {
766    // Exclude any packages in the exclude list
767    for (int i = 0; i < m_excludePackageList.length; i++)
768    {
769      if (name.startsWith(m_excludePackageList[i]))
770      {
771        return true;
772      }
773    }
774
775    // Search through the dependency list. If the class is
776    // already there, skip it
777    return m_dep.get(name) != null;
778  }
779
780
781  /** Add a class to the root set.  */
782  public void addClass(String name)
783  {
784    rootSet.addElement(name);
785  }
786
787
788  /** Mark a class or other resource as processed. */
789  private void setClassProcessed(String name)
790  {
791    // Save the class in our list
792    m_dep.put(name, name);
793  }
794
795
796  /**
797   * Adds the given buffer to the archive with the given
798   * name
799   */
800  private void addToArchive(String name, byte buf[]) throws Exception
801  {
802    // Create a zip entry
803
804    ZipEntry entry = new ZipEntry(name);
805    entry.setSize(buf.length);
806
807    // Add the next entry
808    m_archiveStream.putNextEntry(entry);
809
810    // Write the contents out as well
811    m_archiveStream.write(buf, 0, buf.length);
812    m_archiveStream.closeEntry();
813  }
814
815
816  /**
817   * For display purposes, return a string to indent the proper
818   * number of spaces
819   */
820  private void log(String name)
821  {
822    System.out.println(name);
823  }
824
825
826  /** Return a list of classes found in the current class path for
827   * a given package.
828   */
829  public static String[] packageList(String pkg)
830  {
831    Vector v = new Vector();
832    String pkgpath = pkg.replace('.', '/');
833    for (int i = 0; i < classPath.length; ++i)
834    {
835      String path = classPath[i];
836      String lPath = path.toLowerCase();
837      if (lPath.endsWith(".zip") || lPath.endsWith(".jar"))
838      {
839        try
840        {
841          ZipFile zip = findJar(path);
842          if (zip == null)
843          {
844            continue;
845          }
846          Enumeration e = zip.entries();
847          while (e.hasMoreElements())
848          {
849            ZipEntry ze = (ZipEntry) e.nextElement();
850            String zpath = ze.getName();
851            int plen = pkgpath.length();
852            if (zpath.startsWith(pkgpath) && zpath.endsWith(".class"))
853            {
854              String fname = zpath.substring(plen, zpath.length() - 6);
855              if (fname.lastIndexOf('/') != 0)
856              {
857                continue;
858              }
859              String c = pkg + '.' + fname.substring(1);
860              v.addElement(c);
861            }
862          }
863        }
864        catch (IOException x)
865        {
866          System.err.println(x);
867        }
868        continue;
869      }
870      path = path + filesep + pkgpath + filesep;
871      File file = new File(path);
872      if (file.isDirectory() && file.exists())
873      {
874        String[] s = file.list(classFilter);
875        for (int j = 0; j < s.length; ++j)
876        {
877          String c = pkg + '.' + s[j].substring(0, s[j].length() - 6);
878          v.addElement(c);
879        }
880      }
881    }
882    String[] a = new String[v.size()];
883    v.copyInto(a);
884    return a;
885  }
886
887
888  public static void main(String[] argv) throws Exception
889  {
890    if (argv.length < 1)
891    {
892      System.err.print ("       $Id: ZipLock.java,v 1.10 2000/11/25 04:26:56 stuart Exp $\n" + "        Finds all depencencies of selected classes.\n\n" + "Usage:      java ZipLock [-a archive] [-x prefix] [-p package] class ...\n" + "     -a archive      create archive with result\n" + "       -p package      add all classes in a package\n" + "     -x prefix       exclude classes beginning with prefix\n" + "    class           add a specific class\n" );
893      return;
894    }
895    Vector x = new Vector();
896    String archive = null;
897    ZipLock rc = new ZipLock();
898    for (int i = 0; i < argv.length; ++i)
899    {
900      if (argv[i].startsWith("-"))
901      {
902        int n = argv[i].length();
903        if (n >= 2)
904        {
905          switch (argv[i].charAt(1))
906          {
907            case 'a':
908              if (n > 2)
909              {
910                archive = argv[i].substring(2);
911              }
912              else
913              {
914                archive = argv[ ++i];
915              }
916              continue;
917            case 'x':
918              String exc = (n > 2) ? argv[i].substring(2) : argv[ ++i];
919              x.addElement(exc);
920              continue;
921            case 'p':
922              String pkg = (n > 2) ? argv[i].substring(2) : argv[ ++i];
923              String[] p = packageList(pkg);
924              for (int j = 0; j < p.length; ++j)
925              {
926                rc.addClass(p[j]);
927              }
928
929              continue;
930          }
931        }
932        throw new Exception("Invalid switch " + argv[i]);
933      }
934      rc.addClass(argv[i]);
935    }
936    String[] xs = new String[x.size()];
937    x.copyInto(xs);
938    rc.setExcludes(xs);
939    rc.start(archive);
940  }
941
942  /* **************************** */
943
944
945  public static class IntList
946  {
947    private int[] a = new int[8];
948    private int size = 0;
949
950
951    public final int size()
952    { return size; }
953
954
955    public void add(int i)
956    {
957      if (size >= a.length)
958      {
959        int[] na = new int[ a.length * 2];
960        System.arraycopy(a,0, na,0, size);
961        a = na;
962      }
963      a[size++] = i;
964    }
965
966
967    public void removeAll()
968    { size = 0; }
969
970
971    public int[] elements()
972    {
973      int[] na = new int[size];
974      System.arraycopy(a,0, na,0, size);
975      return na;
976    }
977
978  }
979}