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}