001: /*
002: * Copyright 2003-2005 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.java.util.jar.pack;
027:
028: import java.lang.Error;
029: import java.io.*;
030: import java.text.MessageFormat;
031: import java.util.*;
032: import java.util.jar.*;
033: import java.util.zip.*;
034:
035: /** Command line interface for Pack200.
036: */
037: class Driver {
038: private static final ResourceBundle RESOURCE = ResourceBundle
039: .getBundle("com.sun.java.util.jar.pack.DriverResource");
040:
041: public static void main(String[] ava) throws IOException {
042: ArrayList<String> av = new ArrayList<String>(Arrays.asList(ava));
043:
044: boolean doPack = true;
045: boolean doUnpack = false;
046: boolean doRepack = false;
047: boolean doForceRepack = false;
048: boolean doZip = true;
049: String logFile = null;
050: String verboseProp = Utils.DEBUG_VERBOSE;
051:
052: {
053: // Non-standard, undocumented "--unpack" switch enables unpack mode.
054: String arg0 = av.isEmpty() ? "" : av.get(0);
055: if (arg0.equals("--pack")) {
056: av.remove(0);
057: } else if (arg0.equals("--unpack")) {
058: av.remove(0);
059: doPack = false;
060: doUnpack = true;
061: }
062: }
063:
064: // Collect engine properties here:
065: HashMap<String, String> engProps = new HashMap<String, String>();
066: engProps.put(verboseProp, System.getProperty(verboseProp));
067:
068: String optionMap;
069: String[] propTable;
070: if (doPack) {
071: optionMap = PACK200_OPTION_MAP;
072: propTable = PACK200_PROPERTY_TO_OPTION;
073: } else {
074: optionMap = UNPACK200_OPTION_MAP;
075: propTable = UNPACK200_PROPERTY_TO_OPTION;
076: }
077:
078: // Collect argument properties here:
079: HashMap<String, String> avProps = new HashMap<String, String>();
080: try {
081: for (;;) {
082: String state = parseCommandOptions(av, optionMap,
083: avProps);
084: // Translate command line options to Pack200 properties:
085: eachOpt: for (Iterator<String> opti = avProps.keySet()
086: .iterator(); opti.hasNext();) {
087: String opt = opti.next();
088: String prop = null;
089: for (int i = 0; i < propTable.length; i += 2) {
090: if (opt.equals(propTable[1 + i])) {
091: prop = propTable[0 + i];
092: break;
093: }
094: }
095: if (prop != null) {
096: String val = avProps.get(opt);
097: opti.remove(); // remove opt from avProps
098: if (!prop.endsWith(".")) {
099: // Normal string or boolean.
100: if (!(opt.equals("--verbose") || opt
101: .endsWith("="))) {
102: // Normal boolean; convert to T/F.
103: boolean flag = (val != null);
104: if (opt.startsWith("--no-"))
105: flag = !flag;
106: val = flag ? "true" : "false";
107: }
108: engProps.put(prop, val);
109: } else if (prop.contains(".attribute.")) {
110: for (String val1 : val.split("\0")) {
111: String[] val2 = val1.split("=", 2);
112: engProps.put(prop + val2[0], val2[1]);
113: }
114: } else {
115: // Collection property: pack.pass.file.cli.NNN
116: int idx = 1;
117: for (String val1 : val.split("\0")) {
118: String prop1;
119: do {
120: prop1 = prop + "cli." + (idx++);
121: } while (engProps.containsKey(prop1));
122: engProps.put(prop1, val1);
123: }
124: }
125: }
126: }
127:
128: // See if there is any other action to take.
129: if (state == "--config-file=") {
130: String propFile = av.remove(0);
131: InputStream propIn = new FileInputStream(propFile);
132: Properties fileProps = new Properties();
133: fileProps.load(new BufferedInputStream(propIn));
134: if (engProps.get(verboseProp) != null)
135: fileProps.list(System.out);
136: propIn.close();
137: for (Map.Entry<Object, Object> me : fileProps
138: .entrySet())
139: engProps.put((String) me.getKey(), (String) me
140: .getValue());
141: } else if (state == "--version") {
142: System.out.println(MessageFormat.format(RESOURCE
143: .getString(DriverResource.VERSION),
144: Driver.class.getName(), "1.31, 07/05/05"));
145: return;
146: } else if (state == "--help") {
147: printUsage(doPack, true, System.out);
148: System.exit(1);
149: return;
150: } else {
151: break;
152: }
153: }
154: } catch (IllegalArgumentException ee) {
155: System.err.println(MessageFormat.format(RESOURCE
156: .getString(DriverResource.BAD_ARGUMENT), ee));
157: printUsage(doPack, false, System.err);
158: System.exit(2);
159: return;
160: }
161:
162: // Deal with remaining non-engine properties:
163: for (String opt : avProps.keySet()) {
164: String val = avProps.get(opt);
165: if (opt == "--repack") {
166: doRepack = true;
167: } else if (opt == "--no-gzip") {
168: doZip = (val == null);
169: } else if (opt == "--log-file=") {
170: logFile = val;
171: } else {
172: throw new InternalError(MessageFormat.format(RESOURCE
173: .getString(DriverResource.BAD_OPTION), opt,
174: avProps.get(opt)));
175: }
176: }
177:
178: if (logFile != null && !logFile.equals("")) {
179: if (logFile.equals("-")) {
180: System.setErr(System.out);
181: } else {
182: OutputStream log = new FileOutputStream(logFile);
183: //log = new BufferedOutputStream(out);
184: System.setErr(new PrintStream(log));
185: }
186: }
187:
188: boolean verbose = (engProps.get(verboseProp) != null);
189:
190: String packfile = "";
191: if (!av.isEmpty())
192: packfile = av.remove(0);
193:
194: String jarfile = "";
195: if (!av.isEmpty())
196: jarfile = av.remove(0);
197:
198: String newfile = ""; // output JAR file if --repack
199: String bakfile = ""; // temporary backup of input JAR
200: String tmpfile = ""; // temporary file to be deleted
201: if (doRepack) {
202: // The first argument is the target JAR file.
203: // (Note: *.pac is nonstandard, but may be necessary
204: // if a host OS truncates file extensions.)
205: if (packfile.toLowerCase().endsWith(".pack")
206: || packfile.toLowerCase().endsWith(".pac")
207: || packfile.toLowerCase().endsWith(".gz")) {
208: System.err.println(MessageFormat.format(RESOURCE
209: .getString(DriverResource.BAD_REPACK_OUTPUT),
210: packfile));
211: printUsage(doPack, false, System.err);
212: System.exit(2);
213: }
214: newfile = packfile;
215: // The optional second argument is the source JAR file.
216: if (jarfile.equals("")) {
217: // If only one file is given, it is the only JAR.
218: // It serves as both input and output.
219: jarfile = newfile;
220: }
221: tmpfile = createTempFile(newfile, ".pack").getPath();
222: packfile = tmpfile;
223: doZip = false; // no need to zip the temporary file
224: }
225:
226: if (!av.isEmpty()
227: // Accept jarfiles ending with .jar or .zip.
228: // Accept jarfile of "-" (stdout), but only if unpacking.
229: || !(jarfile.toLowerCase().endsWith(".jar")
230: || jarfile.toLowerCase().endsWith(".zip") || (jarfile
231: .equals("-") && !doPack))) {
232: printUsage(doPack, false, System.err);
233: System.exit(2);
234: return;
235: }
236:
237: if (doRepack)
238: doPack = doUnpack = true;
239: else if (doPack)
240: doUnpack = false;
241:
242: Pack200.Packer jpack = Pack200.newPacker();
243: Pack200.Unpacker junpack = Pack200.newUnpacker();
244:
245: jpack.properties().putAll(engProps);
246: junpack.properties().putAll(engProps);
247: if (doRepack && newfile.equals(jarfile)) {
248: String zipc = getZipComment(jarfile);
249: if (verbose && zipc.length() > 0)
250: System.out
251: .println(MessageFormat
252: .format(
253: RESOURCE
254: .getString(DriverResource.DETECTED_ZIP_COMMENT),
255: zipc));
256: if (zipc.indexOf(Utils.PACK_ZIP_ARCHIVE_MARKER_COMMENT) >= 0) {
257: System.out.println(MessageFormat.format(RESOURCE
258: .getString(DriverResource.SKIP_FOR_REPACKED),
259: jarfile));
260: doPack = false;
261: doUnpack = false;
262: doRepack = false;
263: }
264: }
265:
266: try {
267:
268: if (doPack) {
269: // Mode = Pack.
270: JarFile in = new JarFile(new File(jarfile));
271: OutputStream out;
272: // Packfile must be -, *.gz, *.pack, or *.pac.
273: if (packfile.equals("-")) {
274: out = System.out;
275: // Send warnings, etc., to stderr instead of stdout.
276: System.setOut(System.err);
277: } else if (doZip) {
278: if (!packfile.endsWith(".gz")) {
279: System.err
280: .println(MessageFormat
281: .format(
282: RESOURCE
283: .getString(DriverResource.WRITE_PACK_FILE),
284: packfile));
285: printUsage(doPack, false, System.err);
286: System.exit(2);
287: }
288: out = new FileOutputStream(packfile);
289: out = new BufferedOutputStream(out);
290: out = new GZIPOutputStream(out);
291: } else {
292: if (!packfile.toLowerCase().endsWith(".pack")
293: && !packfile.toLowerCase().endsWith(".pac")) {
294: System.err
295: .println(MessageFormat
296: .format(
297: RESOURCE
298: .getString(DriverResource.WIRTE_PACKGZ_FILE),
299: packfile));
300: printUsage(doPack, false, System.err);
301: System.exit(2);
302: }
303: out = new FileOutputStream(packfile);
304: out = new BufferedOutputStream(out);
305: }
306: jpack.pack(in, out);
307: //in.close(); // p200 closes in but not out
308: out.close();
309: }
310:
311: if (doRepack && newfile.equals(jarfile)) {
312: // If the source and destination are the same,
313: // we will move the input JAR aside while regenerating it.
314: // This allows us to restore it if something goes wrong.
315: File bakf = createTempFile(jarfile, ".bak");
316: // On Windows target must be deleted see 4017593
317: bakf.delete();
318: boolean okBackup = new File(jarfile).renameTo(bakf);
319: if (!okBackup) {
320: throw new Error(
321: MessageFormat
322: .format(
323: RESOURCE
324: .getString(DriverResource.SKIP_FOR_MOVE_FAILED),
325: bakfile));
326: } else {
327: // Open jarfile recovery bracket.
328: bakfile = bakf.getPath();
329: }
330: }
331:
332: if (doUnpack) {
333: // Mode = Unpack.
334: InputStream in;
335: if (packfile.equals("-"))
336: in = System.in;
337: else
338: in = new FileInputStream(new File(packfile));
339: BufferedInputStream inBuf = new BufferedInputStream(in);
340: in = inBuf;
341: if (Utils.isGZIPMagic(Utils.readMagic(inBuf))) {
342: in = new GZIPInputStream(in);
343: }
344: String outfile = newfile.equals("") ? jarfile : newfile;
345: OutputStream fileOut;
346: if (outfile.equals("-"))
347: fileOut = System.out;
348: else
349: fileOut = new FileOutputStream(outfile);
350: fileOut = new BufferedOutputStream(fileOut);
351: JarOutputStream out = new JarOutputStream(fileOut);
352: junpack.unpack(in, out);
353: //in.close(); // p200 closes in but not out
354: out.close();
355: // At this point, we have a good jarfile (or newfile, if -r)
356: }
357:
358: if (!bakfile.equals("")) {
359: // On success, abort jarfile recovery bracket.
360: new File(bakfile).delete();
361: bakfile = "";
362: }
363:
364: } finally {
365: // Close jarfile recovery bracket.
366: if (!bakfile.equals("")) {
367: File jarFile = new File(jarfile);
368: jarFile.delete(); // Win32 requires this, see above
369: new File(bakfile).renameTo(jarFile);
370: }
371: // In all cases, delete temporary *.pack.
372: if (!tmpfile.equals(""))
373: new File(tmpfile).delete();
374: }
375: }
376:
377: static private File createTempFile(String basefile, String suffix)
378: throws IOException {
379: File base = new File(basefile);
380: String prefix = base.getName();
381: if (prefix.length() < 3)
382: prefix += "tmp";
383:
384: File where = base.getParentFile();
385:
386: if (base.getParentFile() == null && suffix.equals(".bak"))
387: where = new File(".").getAbsoluteFile();
388:
389: File f = File.createTempFile(prefix, suffix, where);
390: return f;
391: }
392:
393: static private void printUsage(boolean doPack, boolean full,
394: PrintStream out) {
395: String prog = doPack ? "pack200" : "unpack200";
396: String[] packUsage = (String[]) RESOURCE
397: .getObject(DriverResource.PACK_HELP);
398: String[] unpackUsage = (String[]) RESOURCE
399: .getObject(DriverResource.UNPACK_HELP);
400: String[] usage = doPack ? packUsage : unpackUsage;
401: for (int i = 0; i < usage.length; i++) {
402: out.println(usage[i]);
403: if (!full) {
404: out.println(MessageFormat.format(RESOURCE
405: .getString(DriverResource.MORE_INFO), prog));
406: break;
407: }
408: }
409: }
410:
411: static private String getZipComment(String jarfile)
412: throws IOException {
413: byte[] tail = new byte[1000];
414: long filelen = new File(jarfile).length();
415: if (filelen <= 0)
416: return "";
417: long skiplen = Math.max(0, filelen - tail.length);
418: InputStream in = new FileInputStream(new File(jarfile));
419: try {
420: in.skip(skiplen);
421: in.read(tail);
422: for (int i = tail.length - 4; i >= 0; i--) {
423: if (tail[i + 0] == 'P' && tail[i + 1] == 'K'
424: && tail[i + 2] == 5 && tail[i + 3] == 6) {
425: // Skip sig4, disks4, entries4, clen4, coff4, cmt2
426: i += 4 + 4 + 4 + 4 + 4 + 2;
427: if (i < tail.length)
428: return new String(tail, i, tail.length - i,
429: "UTF8");
430: return "";
431: }
432: }
433: return "";
434: } finally {
435: in.close();
436: }
437: }
438:
439: private static final String PACK200_OPTION_MAP = (""
440: + "--repack $ \n -r +>- @--repack $ \n"
441: + "--no-gzip $ \n -g +>- @--no-gzip $ \n"
442: + "--strip-debug $ \n -G +>- @--strip-debug $ \n"
443: + "--no-keep-file-order $ \n -O +>- @--no-keep-file-order $ \n"
444: + "--segment-limit= *> = \n -S +> @--segment-limit= = \n"
445: + "--effort= *> = \n -E +> @--effort= = \n"
446: + "--deflate-hint= *> = \n -H +> @--deflate-hint= = \n"
447: + "--modification-time= *> = \n -m +> @--modification-time= = \n"
448: + "--pass-file= *> &\0 \n -P +> @--pass-file= &\0 \n"
449: + "--unknown-attribute= *> = \n -U +> @--unknown-attribute= = \n"
450: + "--class-attribute= *> &\0 \n -C +> @--class-attribute= &\0 \n"
451: + "--field-attribute= *> &\0 \n -F +> @--field-attribute= &\0 \n"
452: + "--method-attribute= *> &\0 \n -M +> @--method-attribute= &\0 \n"
453: + "--code-attribute= *> &\0 \n -D +> @--code-attribute= &\0 \n"
454: + "--config-file= *> . \n -f +> @--config-file= . \n"
455:
456: // Negative options as required by CLIP:
457: + "--no-strip-debug !--strip-debug \n"
458: + "--gzip !--no-gzip \n"
459: + "--keep-file-order !--no-keep-file-order \n"
460:
461: // Non-Standard Options
462: + "--verbose $ \n -v +>- @--verbose $ \n"
463: + "--quiet !--verbose \n -q +>- !--verbose \n"
464: + "--log-file= *> = \n -l +> @--log-file= = \n"
465: //+"--java-option= *> = \n -J +> @--java-option= = \n"
466: + "--version . \n -V +> @--version . \n"
467: + "--help . \n -? +> @--help . \n -h +> @--help . \n"
468:
469: // Termination:
470: + "-- . \n" // end option sequence here
471: + "- +? >- . \n" // report error if -XXX present; else use stdout
472: );
473: // Note: Collection options use "\0" as a delimiter between arguments.
474:
475: // For Java version of unpacker (used for testing only):
476: private static final String UNPACK200_OPTION_MAP = (""
477: + "--deflate-hint= *> = \n -H +> @--deflate-hint= = \n"
478: + "--verbose $ \n -v +>- @--verbose $ \n"
479: + "--quiet !--verbose \n -q +>- !--verbose \n"
480: + "--remove-pack-file $ \n -r +>- @--remove-pack-file $ \n"
481: + "--log-file= *> = \n -l +> @--log-file= = \n"
482: + "--config-file= *> . \n -f +> @--config-file= . \n"
483:
484: // Termination:
485: + "-- . \n" // end option sequence here
486: + "- +? >- . \n" // report error if -XXX present; else use stdin
487: + "--version . \n -V +> @--version . \n"
488: + "--help . \n -? +> @--help . \n -h +> @--help . \n");
489:
490: private static final String[] PACK200_PROPERTY_TO_OPTION = {
491: Pack200.Packer.SEGMENT_LIMIT, "--segment-limit=",
492: Pack200.Packer.KEEP_FILE_ORDER, "--no-keep-file-order",
493: Pack200.Packer.EFFORT, "--effort=",
494: Pack200.Packer.DEFLATE_HINT, "--deflate-hint=",
495: Pack200.Packer.MODIFICATION_TIME, "--modification-time=",
496: Pack200.Packer.PASS_FILE_PFX, "--pass-file=",
497: Pack200.Packer.UNKNOWN_ATTRIBUTE, "--unknown-attribute=",
498: Pack200.Packer.CLASS_ATTRIBUTE_PFX, "--class-attribute=",
499: Pack200.Packer.FIELD_ATTRIBUTE_PFX, "--field-attribute=",
500: Pack200.Packer.METHOD_ATTRIBUTE_PFX, "--method-attribute=",
501: Pack200.Packer.CODE_ATTRIBUTE_PFX, "--code-attribute=",
502: //Pack200.Packer.PROGRESS, "--progress=",
503: Utils.DEBUG_VERBOSE, "--verbose",
504: Utils.COM_PREFIX + "strip.debug", "--strip-debug", };
505:
506: private static final String[] UNPACK200_PROPERTY_TO_OPTION = {
507: Pack200.Unpacker.DEFLATE_HINT, "--deflate-hint=",
508: //Pack200.Unpacker.PROGRESS, "--progress=",
509: Utils.DEBUG_VERBOSE, "--verbose",
510: Utils.UNPACK_REMOVE_PACKFILE, "--remove-pack-file", };
511:
512: /*-*
513: * Remove a set of command-line options from args,
514: * storing them in the map in a canonicalized form.
515: * <p>
516: * The options string is a newline-separated series of
517: * option processing specifiers.
518: */
519: private static String parseCommandOptions(List<String> args,
520: String options, Map<String, String> properties) {
521: //System.out.println(args+" // "+properties);
522:
523: String resultString = null;
524:
525: // Convert options string into optLines dictionary.
526: TreeMap<String, String[]> optmap = new TreeMap<String, String[]>();
527: loadOptmap: for (String optline : options.split("\n")) {
528: String[] words = optline.split("\\p{Space}+");
529: if (words.length == 0)
530: continue loadOptmap;
531: String opt = words[0];
532: words[0] = ""; // initial word is not a spec
533: if (opt.length() == 0 && words.length >= 1) {
534: opt = words[1]; // initial "word" is empty due to leading ' '
535: words[1] = "";
536: }
537: if (opt.length() == 0)
538: continue loadOptmap;
539: String[] prevWords = optmap.put(opt, words);
540: if (prevWords != null)
541: throw new RuntimeException(
542: MessageFormat
543: .format(
544: RESOURCE
545: .getString(DriverResource.DUPLICATE_OPTION),
546: optline.trim()));
547: }
548:
549: // State machine for parsing a command line.
550: ListIterator<String> argp = args.listIterator();
551: ListIterator<String> pbp = new ArrayList<String>()
552: .listIterator();
553: doArgs: for (;;) {
554: // One trip through this loop per argument.
555: // Multiple trips per option only if several options per argument.
556: String arg;
557: if (pbp.hasPrevious()) {
558: arg = pbp.previous();
559: pbp.remove();
560: } else if (argp.hasNext()) {
561: arg = argp.next();
562: } else {
563: // No more arguments at all.
564: break doArgs;
565: }
566: tryOpt: for (int optlen = arg.length();; optlen--) {
567: // One time through this loop for each matching arg prefix.
568: String opt;
569: // Match some prefix of the argument to a key in optmap.
570: findOpt: for (;;) {
571: opt = arg.substring(0, optlen);
572: if (optmap.containsKey(opt))
573: break findOpt;
574: if (optlen == 0)
575: break tryOpt;
576: // Decide on a smaller prefix to search for.
577: SortedMap<String, String[]> pfxmap = optmap
578: .headMap(opt);
579: // pfxmap.lastKey is no shorter than any prefix in optmap.
580: int len = pfxmap.isEmpty() ? 0 : pfxmap.lastKey()
581: .length();
582: optlen = Math.min(len, optlen - 1);
583: opt = arg.substring(0, optlen);
584: // (Note: We could cut opt down to its common prefix with
585: // pfxmap.lastKey, but that wouldn't save many cycles.)
586: }
587: opt = opt.intern();
588: assert (arg.startsWith(opt));
589: assert (opt.length() == optlen);
590: String val = arg.substring(optlen); // arg == opt+val
591:
592: // Execute the option processing specs for this opt.
593: // If no actions are taken, then look for a shorter prefix.
594: boolean didAction = false;
595: boolean isError = false;
596:
597: int pbpMark = pbp.nextIndex(); // in case of backtracking
598: String[] specs = optmap.get(opt);
599: eachSpec: for (String spec : specs) {
600: if (spec.length() == 0)
601: continue eachSpec;
602: if (spec.startsWith("#"))
603: break eachSpec;
604: int sidx = 0;
605: char specop = spec.charAt(sidx++);
606:
607: // Deal with '+'/'*' prefixes (spec conditions).
608: boolean ok;
609: switch (specop) {
610: case '+':
611: // + means we want an non-empty val suffix.
612: ok = (val.length() != 0);
613: specop = spec.charAt(sidx++);
614: break;
615: case '*':
616: // * means we accept empty or non-empty
617: ok = true;
618: specop = spec.charAt(sidx++);
619: break;
620: default:
621: // No condition prefix means we require an exact
622: // match, as indicated by an empty val suffix.
623: ok = (val.length() == 0);
624: break;
625: }
626: if (!ok)
627: continue eachSpec;
628:
629: String specarg = spec.substring(sidx);
630: switch (specop) {
631: case '.': // terminate the option sequence
632: resultString = (specarg.length() != 0) ? specarg
633: .intern()
634: : opt;
635: break doArgs;
636: case '?': // abort the option sequence
637: resultString = (specarg.length() != 0) ? specarg
638: .intern()
639: : arg;
640: isError = true;
641: break eachSpec;
642: case '@': // change the effective opt name
643: opt = specarg.intern();
644: break;
645: case '>': // shift remaining arg val to next arg
646: pbp.add(specarg + val); // push a new argument
647: val = "";
648: break;
649: case '!': // negation option
650: String negopt = (specarg.length() != 0) ? specarg
651: .intern()
652: : opt;
653: properties.remove(negopt);
654: properties.put(negopt, null); // leave placeholder
655: didAction = true;
656: break;
657: case '$': // normal "boolean" option
658: String boolval;
659: if (specarg.length() != 0) {
660: // If there is a given spec token, store it.
661: boolval = specarg;
662: } else {
663: String old = properties.get(opt);
664: if (old == null || old.length() == 0) {
665: boolval = "1";
666: } else {
667: // Increment any previous value as a numeral.
668: boolval = ""
669: + (1 + Integer.parseInt(old));
670: }
671: }
672: properties.put(opt, boolval);
673: didAction = true;
674: break;
675: case '=': // "string" option
676: case '&': // "collection" option
677: // Read an option.
678: boolean append = (specop == '&');
679: String strval;
680: if (pbp.hasPrevious()) {
681: strval = pbp.previous();
682: pbp.remove();
683: } else if (argp.hasNext()) {
684: strval = argp.next();
685: } else {
686: resultString = arg + " ?";
687: isError = true;
688: break eachSpec;
689: }
690: if (append) {
691: String old = properties.get(opt);
692: if (old != null) {
693: // Append new val to old with embedded delim.
694: String delim = specarg;
695: if (delim.length() == 0)
696: delim = " ";
697: strval = old + specarg + strval;
698: }
699: }
700: properties.put(opt, strval);
701: didAction = true;
702: break;
703: default:
704: throw new RuntimeException(
705: MessageFormat
706: .format(
707: RESOURCE
708: .getString(DriverResource.BAD_SPEC),
709: opt, spec));
710: }
711: }
712:
713: // Done processing specs.
714: if (didAction && !isError) {
715: continue doArgs;
716: }
717:
718: // The specs should have done something, but did not.
719: while (pbp.nextIndex() > pbpMark) {
720: // Remove anything pushed during these specs.
721: pbp.previous();
722: pbp.remove();
723: }
724:
725: if (isError) {
726: throw new IllegalArgumentException(resultString);
727: }
728:
729: if (optlen == 0) {
730: // We cannot try a shorter matching option.
731: break tryOpt;
732: }
733: }
734:
735: // If we come here, there was no matching option.
736: // So, push back the argument, and return to caller.
737: pbp.add(arg);
738: break doArgs;
739: }
740: // Report number of arguments consumed.
741: args.subList(0, argp.nextIndex()).clear();
742: // Report any unconsumed partial argument.
743: while (pbp.hasPrevious())
744: args.add(0, pbp.previous());
745: //System.out.println(args+" // "+properties+" -> "+resultString);
746: return resultString;
747: }
748: }
|