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