1 /* 2 * Copyright (c) 2014, 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 jdk.tools.jimage; 27 28 import java.io.BufferedOutputStream; 29 import java.io.DataOutputStream; 30 import java.io.File; 31 import java.io.IOException; 32 import java.io.OutputStream; 33 import java.io.PrintWriter; 34 import java.nio.file.Files; 35 import java.nio.file.Path; 36 import java.text.MessageFormat; 37 import java.util.ArrayList; 38 import java.util.LinkedList; 39 import java.util.List; 40 import java.util.Locale; 41 import java.util.MissingResourceException; 42 import java.util.ResourceBundle; 43 import java.util.stream.Collectors; 44 import java.util.stream.Stream; 45 import jdk.internal.jimage.BasicImageReader; 46 import jdk.internal.jimage.BasicImageWriter; 47 import jdk.internal.jimage.ImageHeader; 48 import jdk.internal.jimage.ImageLocation; 49 import jdk.internal.jimage.PackageModuleMap; 50 51 class JImageTask { 52 static class BadArgs extends Exception { 53 static final long serialVersionUID = 8765093759964640723L; // ## re-generate 54 final String key; 55 final Object[] args; 56 boolean showUsage; 57 58 BadArgs(String key, Object... args) { 59 super(JImageTask.getMessage(key, args)); 60 this.key = key; 61 this.args = args; 62 } 63 64 BadArgs showUsage(boolean b) { 65 showUsage = b; 66 return this; 67 } 68 } 69 70 static abstract class Option { 71 final boolean hasArg; 72 final String[] aliases; 73 74 Option(boolean hasArg, String... aliases) { 75 this.hasArg = hasArg; 76 this.aliases = aliases; 77 } 78 79 boolean isHidden() { 80 return false; 81 } 82 83 boolean matches(String opt) { 84 for (String a : aliases) { 85 if (a.equals(opt)) { 86 return true; 87 } else if (opt.startsWith("--") && hasArg && opt.startsWith(a + "=")) { 88 return true; 89 } 90 } 91 return false; 92 } 93 94 boolean ignoreRest() { 95 return false; 96 } 97 98 abstract void process(JImageTask task, String opt, String arg) throws BadArgs; 99 } 100 101 static abstract class HiddenOption extends Option { 102 HiddenOption(boolean hasArg, String... aliases) { 103 super(hasArg, aliases); 104 } 105 106 @Override 107 boolean isHidden() { 108 return true; 109 } 110 } 111 112 static Option[] recognizedOptions = { 113 new Option(true, "--dir") { 114 @Override 115 void process(JImageTask task, String opt, String arg) throws BadArgs { 116 task.options.directory = arg; 117 } 118 }, 119 new HiddenOption(false, "--fullversion") { 120 @Override 121 void process(JImageTask task, String opt, String arg) { 122 task.options.fullVersion = true; 123 } 124 }, 125 new Option(false, "--help") { 126 @Override 127 void process(JImageTask task, String opt, String arg) { 128 task.options.help = true; 129 } 130 }, 131 new Option(false, "--verbose") { 132 @Override 133 void process(JImageTask task, String opt, String arg) throws BadArgs { 134 task.options.verbose = true; 135 } 136 }, 137 new Option(false, "--version") { 138 @Override 139 void process(JImageTask task, String opt, String arg) { 140 task.options.version = true; 141 } 142 }, 143 }; 144 145 static class Options { 146 Task task = Task.LIST; 147 String directory = "."; 148 boolean fullVersion; 149 boolean help; 150 boolean verbose; 151 boolean version; 152 List<File> jimages = new LinkedList<>(); 153 } 154 155 private static final String PROGNAME = "jimage"; 156 private final Options options = new Options(); 157 158 enum Task { 159 RECREATE, 160 EXTRACT, 161 INFO, 162 LIST, 163 VERIFY 164 }; 165 166 private String pad(String string, int width, boolean justifyRight) { 167 int length = string.length(); 168 169 if (length == width) { 170 return string; 171 } 172 173 if (length > width) { 174 return string.substring(0, width); 175 } 176 177 int padding = width - length; 178 179 StringBuilder sb = new StringBuilder(width); 180 if (justifyRight) { 181 for (int i = 0; i < padding; i++) { 182 sb.append(' '); 193 194 return sb.toString(); 195 } 196 197 private String pad(String string, int width) { 198 return pad(string, width, false); 199 } 200 201 private String pad(long value, int width) { 202 return pad(Long.toString(value), width, true); 203 } 204 205 private static final int EXIT_OK = 0; // No errors. 206 private static final int EXIT_ERROR = 1; // Completed but reported errors. 207 private static final int EXIT_CMDERR = 2; // Bad command-line arguments and/or switches. 208 private static final int EXIT_SYSERR = 3; // System error or resource exhaustion. 209 private static final int EXIT_ABNORMAL = 4; // Terminated abnormally. 210 211 int run(String[] args) { 212 if (log == null) { 213 log = new PrintWriter(System.out); 214 } 215 216 try { 217 handleOptions(args); 218 if (options.help) { 219 showHelp(); 220 } 221 if (options.version || options.fullVersion) { 222 showVersion(options.fullVersion); 223 } 224 boolean ok = run(); 225 return ok ? EXIT_OK : EXIT_ERROR; 226 } catch (BadArgs e) { 227 reportError(e.key, e.args); 228 if (e.showUsage) { 229 log.println(getMessage("main.usage.summary", PROGNAME)); 230 } 231 return EXIT_CMDERR; 232 } catch (Exception x) { 233 x.printStackTrace(); 234 return EXIT_ABNORMAL; 235 } finally { 236 log.flush(); 237 } 238 } 239 240 static final String MODULES_ENTRY = PackageModuleMap.MODULES_ENTRY; 241 static final String PACKAGES_ENTRY = "/" + PackageModuleMap.PACKAGES_ENTRY; 242 243 private void recreate() throws IOException, BadArgs { 244 File directory = new File(options.directory); 245 Path dirPath = directory.toPath(); 246 int chop = dirPath.toString().length() + 1; 247 248 if (!directory.isDirectory()) { 249 throw new BadArgs("err.not.a.dir", directory.getAbsolutePath()); 250 } 251 252 if (options.jimages.isEmpty()) { 253 throw new BadArgs("err.jimage.not.specified"); 254 } else if (options.jimages.size() != 1) { 255 throw new BadArgs("err.only.one.jimage"); 256 } 257 258 File jimage = options.jimages.get(0); 259 final List<File> files = new ArrayList<>(); 260 final BasicImageWriter writer = new BasicImageWriter(); 261 final Long longZero = 0L; 262 263 // Note: code sensitive to Netbeans parser crashing. 264 long total = Files.walk(dirPath).reduce(longZero, (Long offset, Path path) -> { 265 long size = 0; 266 String pathString = path.toString(); 267 268 if (pathString.length() < chop || pathString.startsWith(".")) { 269 return 0L; 270 } 271 272 File file = path.toFile(); 273 274 if (file.isFile()) { 275 String name = pathString.substring(chop).replace(File.separatorChar, '/'); 276 277 if (options.verbose) { 278 log.println(name); 279 } 280 281 if (name.endsWith(MODULES_ENTRY) || name.endsWith(PACKAGES_ENTRY)) { 282 try { 283 try (Stream<String> lines = Files.lines(path)) { 284 size = lines.peek(s -> writer.addString(s)).count() * 4; 285 } 286 } catch (IOException ex) { 287 // Caught again when writing file. 288 size = 0; 289 } 290 } else { 291 size = file.length(); 292 } 293 294 writer.addLocation(name, offset, 0L, size); 295 files.add(file); 296 } 297 298 return offset + size; 299 }, 300 (Long offsetL, Long offsetR) -> { return longZero; } ); 301 302 if (jimage.createNewFile()) { 303 try (OutputStream os = Files.newOutputStream(jimage.toPath()); 304 BufferedOutputStream bos = new BufferedOutputStream(os); 305 DataOutputStream out = new DataOutputStream(bos)) { 306 307 byte[] index = writer.getBytes(); 308 out.write(index, 0, index.length); 309 310 for (File file : files) { 311 try { 312 Path path = file.toPath(); 313 String name = path.toString().replace(File.separatorChar, '/'); 314 315 if (name.endsWith(MODULES_ENTRY) || name.endsWith(PACKAGES_ENTRY)) { 316 for (String line: Files.readAllLines(path)) { 317 int off = writer.addString(line); 318 out.writeInt(off); 319 } 320 } else { 321 Files.copy(path, out); 322 } 323 } catch (IOException ex) { 324 throw new BadArgs("err.cannot.read.file", file.getName()); 325 } 326 } 327 } 328 } else { 329 throw new BadArgs("err.jimage.already.exists", jimage.getName()); 330 } 331 332 } 333 334 private void title(File file, BasicImageReader reader) { 335 log.println("jimage: " + file.getName()); 336 } 337 338 private void listTitle(File file, BasicImageReader reader) { 339 title(file, reader); 340 341 if (options.verbose) { 342 log.print(pad("Offset", OFFSET_WIDTH + 1)); 343 log.print(pad("Size", SIZE_WIDTH + 1)); 344 log.print(pad("Compressed", COMPRESSEDSIZE_WIDTH + 1)); 345 log.println(" Entry"); 346 } 347 } 348 349 private interface JImageAction { 350 public void apply(File file, BasicImageReader reader) throws IOException, BadArgs; 351 } 352 353 private interface ResourceAction { 354 public void apply(BasicImageReader reader, String name, ImageLocation location) throws IOException, BadArgs; 355 } 356 357 private void extract(BasicImageReader reader, String name, ImageLocation location) throws IOException, BadArgs { 358 File directory = new File(options.directory); 359 byte[] bytes = reader.getResource(location); 360 File resource = new File(directory, name); 361 File parent = resource.getParentFile(); 362 363 if (parent.exists()) { 364 if (!parent.isDirectory()) { 365 throw new BadArgs("err.cannot.create.dir", parent.getAbsolutePath()); 366 } 367 } else if (!parent.mkdirs()) { 368 throw new BadArgs("err.cannot.create.dir", parent.getAbsolutePath()); 369 } 370 371 if (name.endsWith(MODULES_ENTRY) || name.endsWith(PACKAGES_ENTRY)) { 372 List<String> names = reader.getNames(bytes); 373 Files.write(resource.toPath(), names); 374 } else { 375 Files.write(resource.toPath(), bytes); 376 } 377 } 378 379 private static final int NAME_WIDTH = 40; 380 private static final int NUMBER_WIDTH = 12; 381 private static final int OFFSET_WIDTH = NUMBER_WIDTH; 382 private static final int SIZE_WIDTH = NUMBER_WIDTH; 383 private static final int COMPRESSEDSIZE_WIDTH = NUMBER_WIDTH; 384 385 private void print(String entry, ImageLocation location) { 386 log.print(pad(location.getContentOffset(), OFFSET_WIDTH) + " "); 387 log.print(pad(location.getUncompressedSize(), SIZE_WIDTH) + " "); 388 log.print(pad(location.getCompressedSize(), COMPRESSEDSIZE_WIDTH) + " "); 389 log.println(entry); 390 } 391 392 private void print(BasicImageReader reader, String entry) { 393 if (options.verbose) { 394 print(entry, reader.findLocation(entry)); 395 } else { 396 log.println(entry); 397 } 398 } 399 400 private void info(File file, BasicImageReader reader) { 401 ImageHeader header = reader.getHeader(); 402 403 log.println(" Major Version: " + header.getMajorVersion()); 404 log.println(" Minor Version: " + header.getMinorVersion()); 405 log.println(" Location Count: " + header.getLocationCount()); 406 log.println(" Offsets Size: " + header.getOffsetsSize()); 407 log.println(" Redirects Size: " + header.getRedirectSize()); 408 log.println(" Locations Size: " + header.getLocationsSize()); 409 log.println(" Strings Size: " + header.getStringsSize()); 410 log.println(" Index Size: " + header.getIndexSize()); 411 } 412 413 private void list(BasicImageReader reader, String name, ImageLocation location) { 414 print(reader, name); 415 } 416 417 void verify(BasicImageReader reader, String name, ImageLocation location) { 418 if (name.endsWith(".class")) { 419 byte[] bytes; 420 try { 421 bytes = reader.getResource(location); 422 } catch (IOException ex) { 423 log.println(ex); 424 bytes = null; 425 } 426 427 if (bytes == null || bytes.length <= 4 || 428 (bytes[0] & 0xFF) != 0xCA || 429 (bytes[1] & 0xFF) != 0xFE || 430 (bytes[2] & 0xFF) != 0xBA || 431 (bytes[3] & 0xFF) != 0xBE) { 432 log.print(" NOT A CLASS: "); 433 print(reader, name); 434 } 435 } 436 } 437 438 private void iterate(JImageAction jimageAction, ResourceAction resourceAction) throws IOException, BadArgs { 439 for (File file : options.jimages) { 440 if (!file.exists() || !file.isFile()) { 441 throw new BadArgs("err.not.a.jimage", file.getName()); 442 } 443 444 String path = file.getCanonicalPath(); 445 BasicImageReader reader = BasicImageReader.open(path); 446 447 if (jimageAction != null) { 448 jimageAction.apply(file, reader); 449 } 450 451 if (resourceAction != null) { 452 String[] entryNames = reader.getEntryNames(true); 453 454 for (String name : entryNames) { 455 ImageLocation location = reader.findLocation(name); 456 resourceAction.apply(reader, name, location); 457 } 458 } 459 } 460 } 461 462 private boolean run() throws IOException, BadArgs { 463 switch (options.task) { 464 case RECREATE: 465 recreate(); 466 break; 467 case EXTRACT: 468 iterate(null, this::extract); 469 break; 470 case INFO: 471 iterate(this::info, null); 472 break; 473 case LIST: 474 iterate(this::listTitle, this::list); 475 break; 476 case VERIFY: 477 iterate(this::title, this::verify); 478 break; 479 default: 480 throw new BadArgs("err.invalid.task", options.task.name()).showUsage(true); 481 } 482 return true; 483 } 484 485 private PrintWriter log; 486 void setLog(PrintWriter out) { 487 log = out; 488 } 489 public void handleOptions(String[] args) throws BadArgs { 490 // process options 491 int first = 0; 492 493 if (args.length == 0) { 494 return; 495 } 496 497 String arg = args[first]; 498 499 if (!arg.startsWith("-")) { 500 try { 501 options.task = Enum.valueOf(Task.class, arg.toUpperCase()); 502 first++; 503 } catch (IllegalArgumentException e) { 504 throw new BadArgs("err.invalid.task", arg).showUsage(true); 505 } 506 } 507 508 for (int i = first; i < args.length; i++) { 509 arg = args[i]; 510 511 if (arg.charAt(0) == '-') { 512 Option option = getOption(arg); 513 String param = null; 514 515 if (option.hasArg) { 516 if (arg.startsWith("--") && arg.indexOf('=') > 0) { 517 param = arg.substring(arg.indexOf('=') + 1, arg.length()); 518 } else if (i + 1 < args.length) { 519 param = args[++i]; 520 } 521 522 if (param == null || param.isEmpty() || param.charAt(0) == '-') { 523 throw new BadArgs("err.missing.arg", arg).showUsage(true); 524 } 525 } 526 527 option.process(this, arg, param); 528 529 if (option.ignoreRest()) { 530 i = args.length; 531 } 532 } else { 533 File file = new File(arg); 534 options.jimages.add(file); 535 } 536 } 537 } 538 539 private Option getOption(String name) throws BadArgs { 540 for (Option o : recognizedOptions) { 541 if (o.matches(name)) { 542 return o; 543 } 544 } 545 throw new BadArgs("err.unknown.option", name).showUsage(true); 546 } 547 548 private void reportError(String key, Object... args) { 549 log.println(getMessage("error.prefix") + " " + getMessage(key, args)); 550 } 551 552 private void warning(String key, Object... args) { 553 log.println(getMessage("warn.prefix") + " " + getMessage(key, args)); 554 } 555 556 private void showHelp() { 557 log.println(getMessage("main.usage", PROGNAME)); 558 for (Option o : recognizedOptions) { 559 String name = o.aliases[0].substring(1); // there must always be at least one name 560 name = name.charAt(0) == '-' ? name.substring(1) : name; 561 if (o.isHidden() || name.equals("h")) { 562 continue; 563 } 564 log.println(getMessage("main.opt." + name)); 565 } 566 } 567 568 private void showVersion(boolean full) { 569 log.println(version(full ? "full" : "release")); 570 } 571 572 private String version(String key) { 573 return System.getProperty("java.version"); 574 } 575 576 static String getMessage(String key, Object... args) { 577 try { 578 return MessageFormat.format(ResourceBundleHelper.bundle.getString(key), args); 579 } catch (MissingResourceException e) { 580 throw new InternalError("Missing message: " + key); 581 } 582 } 583 584 private static class ResourceBundleHelper { 585 static final ResourceBundle bundle; 586 587 static { 588 Locale locale = Locale.getDefault(); 589 try { 590 bundle = ResourceBundle.getBundle("jdk.tools.jimage.resources.jimage", locale); 591 } catch (MissingResourceException e) { 592 throw new InternalError("Cannot find jimage resource bundle for locale " + locale); 593 } 594 } 595 } 596 } | 1 /* 2 * Copyright (c) 2014, 2015, 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 jdk.tools.jimage; 27 28 import java.io.File; 29 import java.io.IOException; 30 import java.io.PrintWriter; 31 import java.nio.ByteBuffer; 32 import java.nio.ByteOrder; 33 import java.nio.channels.FileChannel; 34 import java.nio.file.Files; 35 import java.nio.file.Path; 36 import static java.nio.file.StandardOpenOption.READ; 37 import static java.nio.file.StandardOpenOption.WRITE; 38 import java.util.LinkedList; 39 import java.util.List; 40 import jdk.internal.jimage.BasicImageReader; 41 import jdk.internal.jimage.ImageHeader; 42 import static jdk.internal.jimage.ImageHeader.MAGIC; 43 import static jdk.internal.jimage.ImageHeader.MAJOR_VERSION; 44 import static jdk.internal.jimage.ImageHeader.MINOR_VERSION; 45 import jdk.internal.jimage.ImageLocation; 46 import jdk.internal.jimage.ImageModuleData; 47 import jdk.internal.jimage.ImageResourcesTree; 48 import jdk.tools.jimage.TaskHelper.BadArgs; 49 import jdk.tools.jimage.TaskHelper.HiddenOption; 50 import jdk.tools.jimage.TaskHelper.Option; 51 import jdk.tools.jimage.TaskHelper.OptionsHelper; 52 53 class JImageTask { 54 55 static final Option<?>[] recognizedOptions = { 56 new Option<JImageTask>(true, "--dir") { 57 @Override 58 protected void process(JImageTask task, String opt, String arg) throws BadArgs { 59 task.options.directory = arg; 60 } 61 }, 62 new HiddenOption<JImageTask>(false, "--fullversion") { 63 @Override 64 protected void process(JImageTask task, String opt, String arg) { 65 task.options.fullVersion = true; 66 } 67 }, 68 new Option<JImageTask>(false, "--help") { 69 @Override 70 protected void process(JImageTask task, String opt, String arg) { 71 task.options.help = true; 72 } 73 }, 74 75 new Option<JImageTask>(true, "--flags") { 76 @Override 77 protected void process(JImageTask task, String opt, String arg) { 78 task.options.flags = arg; 79 } 80 }, 81 82 new Option<JImageTask>(false, "--verbose") { 83 @Override 84 protected void process(JImageTask task, String opt, String arg) throws BadArgs { 85 task.options.verbose = true; 86 } 87 }, 88 new Option<JImageTask>(false, "--version") { 89 @Override 90 protected void process(JImageTask task, String opt, String arg) { 91 task.options.version = true; 92 } 93 }, 94 }; 95 private static final TaskHelper taskHelper 96 = new TaskHelper("jdk.tools.jimage.resources.jimage"); 97 private static final OptionsHelper<JImageTask> optionsHelper 98 = taskHelper.newOptionsHelper(JImageTask.class, recognizedOptions); 99 100 static class OptionsValues { 101 Task task = Task.LIST; 102 String directory = "."; 103 boolean fullVersion; 104 boolean help; 105 String flags; 106 boolean verbose; 107 boolean version; 108 List<File> jimages = new LinkedList<>(); 109 } 110 111 private static final String PROGNAME = "jimage"; 112 private final OptionsValues options = new OptionsValues(); 113 114 enum Task { 115 EXTRACT, 116 INFO, 117 LIST, 118 RECREATE, 119 SET, 120 VERIFY 121 }; 122 123 private String pad(String string, int width, boolean justifyRight) { 124 int length = string.length(); 125 126 if (length == width) { 127 return string; 128 } 129 130 if (length > width) { 131 return string.substring(0, width); 132 } 133 134 int padding = width - length; 135 136 StringBuilder sb = new StringBuilder(width); 137 if (justifyRight) { 138 for (int i = 0; i < padding; i++) { 139 sb.append(' '); 150 151 return sb.toString(); 152 } 153 154 private String pad(String string, int width) { 155 return pad(string, width, false); 156 } 157 158 private String pad(long value, int width) { 159 return pad(Long.toString(value), width, true); 160 } 161 162 private static final int EXIT_OK = 0; // No errors. 163 private static final int EXIT_ERROR = 1; // Completed but reported errors. 164 private static final int EXIT_CMDERR = 2; // Bad command-line arguments and/or switches. 165 private static final int EXIT_SYSERR = 3; // System error or resource exhaustion. 166 private static final int EXIT_ABNORMAL = 4; // Terminated abnormally. 167 168 int run(String[] args) { 169 if (log == null) { 170 setLog(new PrintWriter(System.out)); 171 } 172 173 try { 174 List<String> unhandled = optionsHelper.handleOptions(this, args); 175 if(!unhandled.isEmpty()) { 176 options.task = Enum.valueOf(Task.class, unhandled.get(0).toUpperCase()); 177 for(int i = 1; i < unhandled.size(); i++) { 178 options.jimages.add(new File(unhandled.get(i))); 179 } 180 } 181 if (options.help) { 182 optionsHelper.showHelp(PROGNAME, "recreate only options:"); 183 } 184 if (options.version || options.fullVersion) { 185 taskHelper.showVersion(options.fullVersion); 186 } 187 boolean ok = run(); 188 return ok ? EXIT_OK : EXIT_ERROR; 189 } catch (BadArgs e) { 190 taskHelper.reportError(e.key, e.args); 191 if (e.showUsage) { 192 log.println(taskHelper.getMessage("main.usage.summary", PROGNAME)); 193 } 194 return EXIT_CMDERR; 195 } catch (Exception x) { 196 x.printStackTrace(); 197 return EXIT_ABNORMAL; 198 } finally { 199 log.flush(); 200 } 201 } 202 203 private void recreate() throws IOException, BadArgs { 204 File directory = new File(options.directory); 205 if (!directory.isDirectory()) { 206 throw taskHelper.newBadArgs("err.not.a.dir", directory.getAbsolutePath()); 207 } 208 Path dirPath = directory.toPath(); 209 if (options.jimages.isEmpty()) { 210 throw taskHelper.newBadArgs("err.jimage.not.specified"); 211 } else if (options.jimages.size() != 1) { 212 throw taskHelper.newBadArgs("err.only.one.jimage"); 213 } 214 215 Path jimage = options.jimages.get(0).toPath(); 216 217 if (jimage.toFile().createNewFile()) { 218 ExtractedImage img = new ExtractedImage(dirPath, log, options.verbose); 219 img.recreateJImage(jimage); 220 } else { 221 throw taskHelper.newBadArgs("err.jimage.already.exists", jimage.getFileName()); 222 } 223 } 224 225 private void title(File file, BasicImageReader reader) { 226 log.println("jimage: " + file.getName()); 227 } 228 229 private void listTitle(File file, BasicImageReader reader) { 230 title(file, reader); 231 232 if (options.verbose) { 233 log.print(pad("Offset", OFFSET_WIDTH + 1)); 234 log.print(pad("Size", SIZE_WIDTH + 1)); 235 log.print(pad("Compressed", COMPRESSEDSIZE_WIDTH + 1)); 236 log.println(" Entry"); 237 } 238 } 239 240 private interface JImageAction { 241 public void apply(File file, BasicImageReader reader) throws IOException, BadArgs; 242 } 243 244 private interface ResourceAction { 245 public void apply(BasicImageReader reader, String name, 246 ImageLocation location) throws IOException, BadArgs; 247 } 248 249 private void extract(BasicImageReader reader, String name, 250 ImageLocation location) throws IOException, BadArgs { 251 File directory = new File(options.directory); 252 byte[] bytes = reader.getResource(location); 253 File resource = new File(directory, name); 254 File parent = resource.getParentFile(); 255 256 if (parent.exists()) { 257 if (!parent.isDirectory()) { 258 throw taskHelper.newBadArgs("err.cannot.create.dir", parent.getAbsolutePath()); 259 } 260 } else if (!parent.mkdirs()) { 261 throw taskHelper.newBadArgs("err.cannot.create.dir", parent.getAbsolutePath()); 262 } 263 264 if (name.endsWith(ImageModuleData.META_DATA_EXTENSION)) { 265 ImageModuleData imageModuleData = new ImageModuleData(reader, bytes); 266 List<String> lines = imageModuleData.fromModulePackages(); 267 Files.write(resource.toPath(), lines); 268 } else { 269 if (!ImageResourcesTree.isTreeInfoResource(name)) { 270 Files.write(resource.toPath(), bytes); 271 } 272 } 273 } 274 275 private static final int NUMBER_WIDTH = 12; 276 private static final int OFFSET_WIDTH = NUMBER_WIDTH; 277 private static final int SIZE_WIDTH = NUMBER_WIDTH; 278 private static final int COMPRESSEDSIZE_WIDTH = NUMBER_WIDTH; 279 280 private void print(String entry, ImageLocation location) { 281 log.print(pad(location.getContentOffset(), OFFSET_WIDTH) + " "); 282 log.print(pad(location.getUncompressedSize(), SIZE_WIDTH) + " "); 283 log.print(pad(location.getCompressedSize(), COMPRESSEDSIZE_WIDTH) + " "); 284 log.println(entry); 285 } 286 287 private void print(BasicImageReader reader, String entry) { 288 if (options.verbose) { 289 print(entry, reader.findLocation(entry)); 290 } else { 291 log.println(entry); 292 } 293 } 294 295 private void info(File file, BasicImageReader reader) throws IOException { 296 ImageHeader header = reader.getHeader(); 297 298 log.println(" Major Version: " + header.getMajorVersion()); 299 log.println(" Minor Version: " + header.getMinorVersion()); 300 log.println(" Flags: " + Integer.toHexString(header.getMinorVersion())); 301 log.println(" Resource Count: " + header.getResourceCount()); 302 log.println(" Table Length: " + header.getTableLength()); 303 log.println(" Offsets Size: " + header.getOffsetsSize()); 304 log.println(" Redirects Size: " + header.getRedirectSize()); 305 log.println(" Locations Size: " + header.getLocationsSize()); 306 log.println(" Strings Size: " + header.getStringsSize()); 307 log.println(" Index Size: " + header.getIndexSize()); 308 } 309 310 private void list(BasicImageReader reader, String name, ImageLocation location) { 311 print(reader, name); 312 } 313 314 void set(File file, BasicImageReader reader) throws BadArgs { 315 try { 316 ImageHeader oldHeader = reader.getHeader(); 317 318 int value = 0; 319 try { 320 value = Integer.valueOf(options.flags); 321 } catch (NumberFormatException ex) { 322 throw taskHelper.newBadArgs("err.flags.not.int", options.flags); 323 } 324 325 ImageHeader newHeader = new ImageHeader(MAGIC, MAJOR_VERSION, MINOR_VERSION, 326 value, 327 oldHeader.getResourceCount(), oldHeader.getTableLength(), 328 oldHeader.getLocationsSize(), oldHeader.getStringsSize()); 329 330 ByteBuffer buffer = ByteBuffer.allocate(ImageHeader.getHeaderSize()); 331 buffer.order(ByteOrder.nativeOrder()); 332 newHeader.writeTo(buffer); 333 buffer.rewind(); 334 335 try (FileChannel channel = FileChannel.open(file.toPath(), READ, WRITE)) { 336 channel.write(buffer, 0); 337 } 338 } catch (IOException ex) { 339 throw taskHelper.newBadArgs("err.cannot.update.file", file.getName()); 340 } 341 } 342 343 void verify(BasicImageReader reader, String name, ImageLocation location) { 344 if (name.endsWith(".class")) { 345 byte[] bytes = reader.getResource(location); 346 347 if (bytes == null || bytes.length <= 4 || 348 (bytes[0] & 0xFF) != 0xCA || 349 (bytes[1] & 0xFF) != 0xFE || 350 (bytes[2] & 0xFF) != 0xBA || 351 (bytes[3] & 0xFF) != 0xBE) { 352 log.print(" NOT A CLASS: "); 353 print(reader, name); 354 } 355 } 356 } 357 358 private void iterate(JImageAction jimageAction, 359 ResourceAction resourceAction) throws IOException, BadArgs { 360 for (File file : options.jimages) { 361 if (!file.exists() || !file.isFile()) { 362 throw taskHelper.newBadArgs("err.not.a.jimage", file.getName()); 363 } 364 365 String path = file.getCanonicalPath(); 366 BasicImageReader reader = BasicImageReader.open(path); 367 368 if (jimageAction != null) { 369 jimageAction.apply(file, reader); 370 } 371 372 if (resourceAction != null) { 373 String[] entryNames = reader.getEntryNames(); 374 375 for (String name : entryNames) { 376 if (!ImageResourcesTree.isTreeInfoResource(name)) { 377 ImageLocation location = reader.findLocation(name); 378 resourceAction.apply(reader, name, location); 379 } 380 } 381 } 382 } 383 } 384 385 private boolean run() throws IOException, BadArgs { 386 switch (options.task) { 387 case EXTRACT: 388 iterate(null, this::extract); 389 break; 390 case INFO: 391 iterate(this::info, null); 392 break; 393 case LIST: 394 iterate(this::listTitle, this::list); 395 break; 396 case RECREATE: 397 recreate(); 398 break; 399 case SET: 400 iterate(this::set, null); 401 break; 402 case VERIFY: 403 iterate(this::title, this::verify); 404 break; 405 default: 406 throw taskHelper.newBadArgs("err.invalid.task", options.task.name()).showUsage(true); 407 } 408 return true; 409 } 410 411 private PrintWriter log; 412 void setLog(PrintWriter out) { 413 log = out; 414 taskHelper.setLog(log); 415 } 416 } |