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