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