1 /* 2 * Copyright (c) 1997, 2017, 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.tools.internal.xjc; 27 28 import java.io.BufferedReader; 29 import java.io.File; 30 import java.io.FileInputStream; 31 import java.io.IOException; 32 import java.io.InputStreamReader; 33 import java.io.PrintWriter; 34 import java.io.StringWriter; 35 import java.net.MalformedURLException; 36 import java.net.URL; 37 import java.net.URLClassLoader; 38 import java.security.AccessController; 39 import java.security.PrivilegedAction; 40 import java.text.SimpleDateFormat; 41 import java.util.ArrayList; 42 import java.util.Date; 43 import java.util.Enumeration; 44 import java.util.HashSet; 45 import java.util.List; 46 import java.util.ServiceLoader; 47 import java.util.Set; 48 49 import com.sun.codemodel.internal.CodeWriter; 50 import com.sun.codemodel.internal.JPackage; 51 import com.sun.codemodel.internal.JResourceFile; 52 import com.sun.codemodel.internal.writer.FileCodeWriter; 53 import com.sun.codemodel.internal.writer.PrologCodeWriter; 54 import com.sun.istack.internal.tools.DefaultAuthenticator; 55 import com.sun.tools.internal.xjc.api.ClassNameAllocator; 56 import com.sun.tools.internal.xjc.api.SpecVersion; 57 import com.sun.tools.internal.xjc.generator.bean.field.FieldRendererFactory; 58 import com.sun.tools.internal.xjc.model.Model; 59 import com.sun.tools.internal.xjc.reader.Util; 60 import com.sun.xml.internal.bind.api.impl.NameConverter; 61 import java.net.URI; 62 import java.net.URISyntaxException; 63 import java.nio.charset.Charset; 64 import java.nio.charset.IllegalCharsetNameException; 65 import java.util.Locale; 66 import java.util.logging.Level; 67 import java.util.logging.Logger; 68 69 import org.xml.sax.EntityResolver; 70 import org.xml.sax.InputSource; 71 72 /** 73 * Global options. 74 * 75 * <p> 76 * This class stores invocation configuration for XJC. 77 * The configuration in this class should be abstract enough so that 78 * it could be parsed from both command-line or Ant. 79 */ 80 public class Options { 81 /** 82 * If "-debug" is specified. 83 */ 84 public boolean debugMode; 85 86 /** 87 * If the "-verbose" option is specified. 88 */ 89 public boolean verbose; 90 91 /** 92 * If the "-quiet" option is specified. 93 */ 94 public boolean quiet; 95 96 /** 97 * If the -readOnly option is specified. 98 */ 99 public boolean readOnly; 100 101 /** 102 * No file header comment (to be more friendly with diff.) 103 */ 104 public boolean noFileHeader; 105 106 /** 107 * When on, fixes getter/setter generation to match the Bean Introspection API 108 */ 109 public boolean enableIntrospection; 110 111 /** 112 * When on, generates content property for types with multiple xs:any derived elements (which is supposed to be correct behaviour) 113 */ 114 public boolean contentForWildcard; 115 116 /** 117 * Encoding to be used by generated java sources, null for platform default. 118 */ 119 public String encoding; 120 121 /** 122 * If true XML security features when parsing XML documents will be disabled. 123 * The default value is false. 124 * <p> 125 * Boolean 126 * 127 * @since 2.2.6 128 */ 129 public boolean disableXmlSecurity; 130 131 /** 132 * Check the source schemas with extra scrutiny. 133 * The exact meaning depends on the schema language. 134 */ 135 public boolean strictCheck = true; 136 137 /** 138 * If -explicit-annotation option is specified. 139 * <p> 140 * This generates code that works around issues specific to 1.4 runtime. 141 */ 142 public boolean runtime14 = false; 143 144 /** 145 * If true, try to resolve name conflicts automatically by assigning mechanical numbers. 146 */ 147 public boolean automaticNameConflictResolution = false; 148 149 /** 150 * strictly follow the compatibility rules and reject schemas that 151 * contain features from App. E.2, use vendor binding extensions 152 */ 153 public static final int STRICT = 1; 154 /** 155 * loosely follow the compatibility rules and allow the use of vendor 156 * binding extensions 157 */ 158 public static final int EXTENSION = 2; 159 160 /** 161 * this switch determines how carefully the compiler will follow 162 * the compatibility rules in the spec. Either {@code STRICT} 163 * or {@code EXTENSION}. 164 */ 165 public int compatibilityMode = STRICT; 166 167 public boolean isExtensionMode() { 168 return compatibilityMode == EXTENSION; 169 } 170 171 private static final Logger logger = com.sun.xml.internal.bind.Util.getClassLogger(); 172 173 /** 174 * Generates output for the specified version of the runtime. 175 */ 176 public SpecVersion target = SpecVersion.LATEST; 177 178 179 public Options() { 180 try { 181 Class.forName("javax.xml.bind.JAXBPermission"); 182 } catch (ClassNotFoundException cnfe) { 183 target = SpecVersion.V2_1; 184 } 185 } 186 187 /** 188 * Target directory when producing files. 189 * <p> 190 * This field is not used when XJC is driven through the XJC API. 191 * Plugins that need to generate extra files should do so by using 192 * {@link JPackage#addResourceFile(JResourceFile)}. 193 */ 194 public File targetDir = new File("."); 195 196 /** 197 * On JDK 8 an odler stores {@code CatalogResolver}, but the field 198 * type is made to {@link EntityResolver} so that XJC can be 199 * used even if resolver.jar is not available in the classpath. 200 */ 201 public EntityResolver entityResolver = null; 202 203 /** 204 * Type of input schema language. One of the {@code SCHEMA_XXX} 205 * constants. 206 */ 207 private Language schemaLanguage = null; 208 209 /** 210 * The -p option that should control the default Java package that 211 * will contain the generated code. Null if unspecified. 212 */ 213 public String defaultPackage = null; 214 215 /** 216 * Similar to the -p option, but this one works with a lower priority, 217 * and customizations overrides this. Used by JAX-RPC. 218 */ 219 public String defaultPackage2 = null; 220 221 /** 222 * Input schema files as a list of {@link InputSource}s. 223 */ 224 private final List<InputSource> grammars = new ArrayList<>(); 225 226 private final List<InputSource> bindFiles = new ArrayList<>(); 227 228 // Proxy setting. 229 private String proxyHost = null; 230 private String proxyPort = null; 231 public String proxyAuth = null; 232 233 /** 234 * {@link Plugin}s that are enabled in this compilation. 235 */ 236 public final List<Plugin> activePlugins = new ArrayList<>(); 237 238 /** 239 * All discovered {@link Plugin}s. 240 * This is lazily parsed, so that we can take '-cp' option into account. 241 * 242 * @see #getAllPlugins() 243 */ 244 private List<Plugin> allPlugins; 245 246 /** 247 * Set of URIs that plug-ins recognize as extension bindings. 248 */ 249 public final Set<String> pluginURIs = new HashSet<>(); 250 251 /** 252 * This allocator has the final say on deciding the class name. 253 */ 254 public ClassNameAllocator classNameAllocator; 255 256 /** 257 * This switch controls whether or not xjc will generate package level annotations 258 */ 259 public boolean packageLevelAnnotations = true; 260 261 /** 262 * This {@link FieldRendererFactory} determines how the fields are generated. 263 */ 264 private FieldRendererFactory fieldRendererFactory = new FieldRendererFactory(); 265 /** 266 * Used to detect if two {@link Plugin}s try to overwrite {@link #fieldRendererFactory}. 267 */ 268 private Plugin fieldRendererFactoryOwner = null; 269 270 /** 271 * If this is non-null, we use this {@link NameConverter} over the one 272 * given in the schema/binding. 273 */ 274 private NameConverter nameConverter = null; 275 /** 276 * Used to detect if two {@link Plugin}s try to overwrite {@link #nameConverter}. 277 */ 278 private Plugin nameConverterOwner = null; 279 280 /** 281 * Java module name in {@code module-info.java}. 282 */ 283 private String javaModule = null; 284 285 /** 286 * Gets the active {@link FieldRendererFactory} that shall be used to build {@link Model}. 287 * 288 * @return always non-null. 289 */ 290 public FieldRendererFactory getFieldRendererFactory() { 291 return fieldRendererFactory; 292 } 293 294 /** 295 * Sets the {@link FieldRendererFactory}. 296 * <p> 297 * <p> 298 * This method is for plugins to call to set a custom {@link FieldRendererFactory}. 299 * 300 * @param frf The {@link FieldRendererFactory} to be installed. Must not be null. 301 * @param owner Identifies the plugin that owns this {@link FieldRendererFactory}. 302 * When two {@link Plugin}s try to call this method, this allows XJC 303 * to report it as a user-friendly error message. 304 * @throws BadCommandLineException If a conflit happens, this exception carries a user-friendly error 305 * message, indicating a conflict. 306 */ 307 public void setFieldRendererFactory(FieldRendererFactory frf, Plugin owner) throws BadCommandLineException { 308 // since this method is for plugins, make it bit more fool-proof than usual 309 if (frf == null) 310 throw new IllegalArgumentException(); 311 if (fieldRendererFactoryOwner != null) { 312 throw new BadCommandLineException( 313 Messages.format(Messages.FIELD_RENDERER_CONFLICT, 314 fieldRendererFactoryOwner.getOptionName(), 315 owner.getOptionName())); 316 } 317 this.fieldRendererFactoryOwner = owner; 318 this.fieldRendererFactory = frf; 319 } 320 321 322 /** 323 * Gets the active {@link NameConverter} that shall be used to build {@link Model}. 324 * 325 * @return can be null, in which case it's up to the binding. 326 */ 327 public NameConverter getNameConverter() { 328 return nameConverter; 329 } 330 331 /** 332 * Sets the {@link NameConverter}. 333 * <p> 334 * <p> 335 * This method is for plugins to call to set a custom {@link NameConverter}. 336 * 337 * @param nc The {@link NameConverter} to be installed. Must not be null. 338 * @param owner Identifies the plugin that owns this {@link NameConverter}. 339 * When two {@link Plugin}s try to call this method, this allows XJC 340 * to report it as a user-friendly error message. 341 * @throws BadCommandLineException If a conflit happens, this exception carries a user-friendly error 342 * message, indicating a conflict. 343 */ 344 public void setNameConverter(NameConverter nc, Plugin owner) throws BadCommandLineException { 345 // since this method is for plugins, make it bit more fool-proof than usual 346 if (nc == null) 347 throw new IllegalArgumentException(); 348 if (nameConverter != null) { 349 throw new BadCommandLineException( 350 Messages.format(Messages.NAME_CONVERTER_CONFLICT, 351 nameConverterOwner.getOptionName(), 352 owner.getOptionName())); 353 } 354 this.nameConverterOwner = owner; 355 this.nameConverter = nc; 356 } 357 358 /** 359 * Gets all the {@link Plugin}s discovered so far. 360 * <p> 361 * <p> 362 * A plugins are enumerated when this method is called for the first time, 363 * by taking {@link #classpaths} into account. That means 364 * "-cp plugin.jar" has to come before you specify options to enable it. 365 * 366 * @return 367 */ 368 public List<Plugin> getAllPlugins() { 369 if (allPlugins == null) { 370 allPlugins = findServices(Plugin.class); 371 } 372 373 return allPlugins; 374 } 375 376 public Language getSchemaLanguage() { 377 if (schemaLanguage == null) 378 schemaLanguage = guessSchemaLanguage(); 379 return schemaLanguage; 380 } 381 382 public void setSchemaLanguage(Language _schemaLanguage) { 383 this.schemaLanguage = _schemaLanguage; 384 } 385 386 /** 387 * Input schema files. 388 * 389 * @return 390 */ 391 public InputSource[] getGrammars() { 392 return grammars.toArray(new InputSource[grammars.size()]); 393 } 394 395 /** 396 * Adds a new input schema. 397 * 398 * @param is 399 */ 400 public void addGrammar(InputSource is) { 401 grammars.add(absolutize(is)); 402 } 403 404 private InputSource fileToInputSource(File source) { 405 try { 406 String url = source.toURL().toExternalForm(); 407 return new InputSource(Util.escapeSpace(url)); 408 } catch (MalformedURLException e) { 409 return new InputSource(source.getPath()); 410 } 411 } 412 413 public void addGrammar(File source) { 414 addGrammar(fileToInputSource(source)); 415 } 416 417 /** 418 * Recursively scan directories and add all XSD files in it. 419 * 420 * @param dir 421 */ 422 public void addGrammarRecursive(File dir) { 423 addRecursive(dir, ".xsd", grammars); 424 } 425 426 private void addRecursive(File dir, String suffix, List<InputSource> result) { 427 File[] files = dir.listFiles(); 428 if (files == null) return; // work defensively 429 430 for (File f : files) { 431 if (f.isDirectory()) 432 addRecursive(f, suffix, result); 433 else if (f.getPath().endsWith(suffix)) 434 result.add(absolutize(fileToInputSource(f))); 435 } 436 } 437 438 439 private InputSource absolutize(InputSource is) { 440 // absolutize all the system IDs in the input, so that we can map system IDs to DOM trees. 441 try { 442 URL baseURL = new File(".").getCanonicalFile().toURL(); 443 is.setSystemId(new URL(baseURL, is.getSystemId()).toExternalForm()); 444 } catch (IOException e) { 445 logger.log(Level.FINE, "{0}, {1}", new Object[]{is.getSystemId(), e.getLocalizedMessage()}); 446 } 447 return is; 448 } 449 450 /** 451 * Input external binding files. 452 * 453 * @return 454 */ 455 public InputSource[] getBindFiles() { 456 return bindFiles.toArray(new InputSource[bindFiles.size()]); 457 } 458 459 /** 460 * Adds a new binding file. 461 * 462 * @param is 463 */ 464 public void addBindFile(InputSource is) { 465 bindFiles.add(absolutize(is)); 466 } 467 468 /** 469 * Adds a new binding file. 470 * 471 * @param bindFile 472 */ 473 public void addBindFile(File bindFile) { 474 bindFiles.add(fileToInputSource(bindFile)); 475 } 476 477 /** 478 * Recursively scan directories and add all ".xjb" files in it. 479 * 480 * @param dir 481 */ 482 public void addBindFileRecursive(File dir) { 483 addRecursive(dir, ".xjb", bindFiles); 484 } 485 486 public final List<URL> classpaths = new ArrayList<>(); 487 488 /** 489 * Gets a classLoader that can load classes specified via the 490 * -classpath option. 491 * 492 * @param parent 493 * @return 494 */ 495 public ClassLoader getUserClassLoader(ClassLoader parent) { 496 if (classpaths.isEmpty()) 497 return parent; 498 return new URLClassLoader( 499 classpaths.toArray(new URL[classpaths.size()]), parent); 500 } 501 502 /** 503 * Gets Java module name option. 504 * 505 * @return Java module name option or {@code null} if this option was not set. 506 */ 507 public String getModuleName() { 508 return javaModule; 509 } 510 511 /** 512 * Parses an option {@code args[i]} and return 513 * the number of tokens consumed. 514 * 515 * @param args 516 * @param i 517 * @return 0 if the argument is not understood. Returning 0 518 * will let the caller report an error. 519 * @throws BadCommandLineException If the callee wants to provide a custom message for an error. 520 */ 521 public int parseArgument(String[] args, int i) throws BadCommandLineException { 522 if (args[i].equals("-classpath") || args[i].equals("-cp")) { 523 String a = requireArgument(args[i], args, ++i); 524 for (String p : a.split(File.pathSeparator)) { 525 File file = new File(p); 526 try { 527 classpaths.add(file.toURL()); 528 } catch (MalformedURLException e) { 529 throw new BadCommandLineException( 530 Messages.format(Messages.NOT_A_VALID_FILENAME, file), e); 531 } 532 } 533 return 2; 534 } 535 if (args[i].equals("-d")) { 536 targetDir = new File(requireArgument("-d", args, ++i)); 537 if (!targetDir.exists()) 538 throw new BadCommandLineException( 539 Messages.format(Messages.NON_EXISTENT_DIR, targetDir)); 540 return 2; 541 } 542 if (args[i].equals("-readOnly")) { 543 readOnly = true; 544 return 1; 545 } 546 if (args[i].equals("-p")) { 547 defaultPackage = requireArgument("-p", args, ++i); 548 if (defaultPackage.length() == 0) { // user specified default package 549 // there won't be any package to annotate, so disable them 550 // automatically as a usability feature 551 packageLevelAnnotations = false; 552 } 553 return 2; 554 } 555 if (args[i].equals("-m")) { 556 javaModule = requireArgument("-m", args, ++i); 557 return 2; 558 } 559 if (args[i].equals("-debug")) { 560 debugMode = true; 561 verbose = true; 562 return 1; 563 } 564 if (args[i].equals("-nv")) { 565 strictCheck = false; 566 return 1; 567 } 568 if (args[i].equals("-npa")) { 569 packageLevelAnnotations = false; 570 return 1; 571 } 572 if (args[i].equals("-no-header")) { 573 noFileHeader = true; 574 return 1; 575 } 576 if (args[i].equals("-verbose")) { 577 verbose = true; 578 return 1; 579 } 580 if (args[i].equals("-quiet")) { 581 quiet = true; 582 return 1; 583 } 584 if (args[i].equals("-XexplicitAnnotation")) { 585 runtime14 = true; 586 return 1; 587 } 588 if (args[i].equals("-enableIntrospection")) { 589 enableIntrospection = true; 590 return 1; 591 } 592 if (args[i].equals("-disableXmlSecurity")) { 593 disableXmlSecurity = true; 594 return 1; 595 } 596 if (args[i].equals("-contentForWildcard")) { 597 contentForWildcard = true; 598 return 1; 599 } 600 if (args[i].equals("-XautoNameResolution")) { 601 automaticNameConflictResolution = true; 602 return 1; 603 } 604 if (args[i].equals("-b")) { 605 addFile(requireArgument("-b", args, ++i), bindFiles, ".xjb"); 606 return 2; 607 } 608 if (args[i].equals("-dtd")) { 609 schemaLanguage = Language.DTD; 610 return 1; 611 } 612 if (args[i].equals("-xmlschema")) { 613 schemaLanguage = Language.XMLSCHEMA; 614 return 1; 615 } 616 if (args[i].equals("-wsdl")) { 617 schemaLanguage = Language.WSDL; 618 return 1; 619 } 620 if (args[i].equals("-extension")) { 621 compatibilityMode = EXTENSION; 622 return 1; 623 } 624 if (args[i].equals("-target")) { 625 String token = requireArgument("-target", args, ++i); 626 target = SpecVersion.parse(token); 627 if (target == null) 628 throw new BadCommandLineException(Messages.format(Messages.ILLEGAL_TARGET_VERSION, token)); 629 return 2; 630 } 631 if (args[i].equals("-httpproxyfile")) { 632 if (i == args.length - 1 || args[i + 1].startsWith("-")) { 633 throw new BadCommandLineException( 634 Messages.format(Messages.MISSING_PROXYFILE)); 635 } 636 637 File file = new File(args[++i]); 638 if (!file.exists()) { 639 throw new BadCommandLineException( 640 Messages.format(Messages.NO_SUCH_FILE, file)); 641 } 642 643 try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"))) { 644 parseProxy(in.readLine()); 645 } catch (IOException e) { 646 throw new BadCommandLineException( 647 Messages.format(Messages.FAILED_TO_PARSE, file, e.getMessage()), e); 648 } 649 650 return 2; 651 } 652 if (args[i].equals("-httpproxy")) { 653 if (i == args.length - 1 || args[i + 1].startsWith("-")) { 654 throw new BadCommandLineException( 655 Messages.format(Messages.MISSING_PROXY)); 656 } 657 658 parseProxy(args[++i]); 659 return 2; 660 } 661 if (args[i].equals("-host")) { 662 proxyHost = requireArgument("-host", args, ++i); 663 return 2; 664 } 665 if (args[i].equals("-port")) { 666 proxyPort = requireArgument("-port", args, ++i); 667 return 2; 668 } 669 if (args[i].equals("-catalog")) { 670 // use Sun's "XML Entity and URI Resolvers" by Norman Walsh 671 // to resolve external entities. 672 // https://xerces.apache.org/xml-commons/components/resolver/resolver-article.html 673 674 File catalogFile = new File(requireArgument("-catalog", args, ++i)); 675 try { 676 addCatalog(catalogFile); 677 } catch (IOException e) { 678 throw new BadCommandLineException( 679 Messages.format(Messages.FAILED_TO_PARSE, catalogFile, e.getMessage()), e); 680 } 681 return 2; 682 } 683 if (args[i].equals("-Xtest-class-name-allocator")) { 684 classNameAllocator = new ClassNameAllocator() { 685 @Override 686 public String assignClassName(String packageName, String className) { 687 System.out.printf("assignClassName(%s,%s)\n", packageName, className); 688 return className + "_Type"; 689 } 690 }; 691 return 1; 692 } 693 694 if (args[i].equals("-encoding")) { 695 encoding = requireArgument("-encoding", args, ++i); 696 try { 697 if (!Charset.isSupported(encoding)) { 698 throw new BadCommandLineException( 699 Messages.format(Messages.UNSUPPORTED_ENCODING, encoding)); 700 } 701 } catch (IllegalCharsetNameException icne) { 702 throw new BadCommandLineException( 703 Messages.format(Messages.UNSUPPORTED_ENCODING, encoding)); 704 } 705 return 2; 706 } 707 708 // see if this is one of the extensions 709 for (Plugin plugin : getAllPlugins()) { 710 try { 711 if (('-' + plugin.getOptionName()).equals(args[i])) { 712 activePlugins.add(plugin); 713 plugin.onActivated(this); 714 pluginURIs.addAll(plugin.getCustomizationURIs()); 715 716 // give the plugin a chance to parse arguments to this option. 717 // this is new in 2.1, and due to the backward compatibility reason, 718 // if plugin didn't understand it, we still return 1 to indicate 719 // that this option is consumed. 720 int r = plugin.parseArgument(this, args, i); 721 if (r != 0) 722 return r; 723 else 724 return 1; 725 } 726 727 int r = plugin.parseArgument(this, args, i); 728 if (r != 0) return r; 729 } catch (IOException e) { 730 throw new BadCommandLineException(e.getMessage(), e); 731 } 732 } 733 734 return 0; // unrecognized 735 } 736 737 private void parseProxy(String text) throws BadCommandLineException { 738 int i = text.lastIndexOf('@'); 739 int j = text.lastIndexOf(':'); 740 741 if (i > 0) { 742 proxyAuth = text.substring(0, i); 743 if (j > i) { 744 proxyHost = text.substring(i + 1, j); 745 proxyPort = text.substring(j + 1); 746 } else { 747 proxyHost = text.substring(i + 1); 748 proxyPort = "80"; 749 } 750 } else { 751 //no auth info 752 if (j < 0) { 753 //no port 754 proxyHost = text; 755 proxyPort = "80"; 756 } else { 757 proxyHost = text.substring(0, j); 758 proxyPort = text.substring(j + 1); 759 } 760 } 761 try { 762 Integer.valueOf(proxyPort); 763 } catch (NumberFormatException e) { 764 throw new BadCommandLineException(Messages.format(Messages.ILLEGAL_PROXY, text)); 765 } 766 } 767 768 /** 769 * Obtains an operand and reports an error if it's not there. 770 * 771 * @param optionName 772 * @param args 773 * @param i 774 * @return 775 * @throws com.sun.tools.internal.xjc.BadCommandLineException 776 */ 777 public String requireArgument(String optionName, String[] args, int i) throws BadCommandLineException { 778 if (i == args.length || args[i].startsWith("-")) { 779 throw new BadCommandLineException( 780 Messages.format(Messages.MISSING_OPERAND, optionName)); 781 } 782 return args[i]; 783 } 784 785 /** 786 * Parses a token to a file (or a set of files) 787 * and add them as {@link InputSource} to the specified list. 788 * 789 * @param suffix If the given token is a directory name, we do a recursive search 790 * and find all files that have the given suffix. 791 */ 792 private void addFile(String name, List<InputSource> target, String suffix) throws BadCommandLineException { 793 Object src; 794 try { 795 src = Util.getFileOrURL(name); 796 } catch (IOException e) { 797 throw new BadCommandLineException( 798 Messages.format(Messages.NOT_A_FILE_NOR_URL, name)); 799 } 800 if (src instanceof URL) { 801 target.add(absolutize(new InputSource(Util.escapeSpace(((URL) src).toExternalForm())))); 802 } else { 803 File fsrc = (File) src; 804 if (fsrc.isDirectory()) { 805 addRecursive(fsrc, suffix, target); 806 } else { 807 target.add(absolutize(fileToInputSource(fsrc))); 808 } 809 } 810 } 811 812 // Since javax.xml.catalog is unmodifiable we need to track catalog 813 // URLs added and create new catalog each time addCatalog is called 814 private final ArrayList<URI> catalogUrls = new ArrayList<>(); 815 816 /** 817 * Adds a new catalog file.Use created or existed resolver to parse new catalog file. 818 * 819 * @param catalogFile 820 * @throws java.io.IOException 821 */ 822 public void addCatalog(File catalogFile) throws IOException { 823 URI newUri = catalogFile.toURI(); 824 if (!catalogUrls.contains(newUri)) { 825 catalogUrls.add(newUri); 826 } 827 entityResolver = CatalogUtil.getCatalog(entityResolver, catalogFile, catalogUrls); 828 } 829 830 /** 831 * Parses arguments and fill fields of this object. 832 * 833 * @param args 834 * @throws BadCommandLineException thrown when there's a problem in the command-line arguments 835 */ 836 public void parseArguments(String[] args) throws BadCommandLineException { 837 838 for (int i = 0; i < args.length; i++) { 839 if (args[i].length() == 0) 840 throw new BadCommandLineException(); 841 if (args[i].charAt(0) == '-') { 842 int j = parseArgument(args, i); 843 if (j == 0) 844 throw new BadCommandLineException( 845 Messages.format(Messages.UNRECOGNIZED_PARAMETER, args[i])); 846 i += (j - 1); 847 } else { 848 if (args[i].endsWith(".jar")) 849 scanEpisodeFile(new File(args[i])); 850 else 851 addFile(args[i], grammars, ".xsd"); 852 } 853 } 854 855 // configure proxy 856 if (proxyHost != null || proxyPort != null) { 857 if (proxyHost != null && proxyPort != null) { 858 System.setProperty("http.proxyHost", proxyHost); 859 System.setProperty("http.proxyPort", proxyPort); 860 System.setProperty("https.proxyHost", proxyHost); 861 System.setProperty("https.proxyPort", proxyPort); 862 } else if (proxyHost == null) { 863 throw new BadCommandLineException( 864 Messages.format(Messages.MISSING_PROXYHOST)); 865 } else { 866 throw new BadCommandLineException( 867 Messages.format(Messages.MISSING_PROXYPORT)); 868 } 869 if (proxyAuth != null) { 870 DefaultAuthenticator.getAuthenticator().setProxyAuth(proxyAuth); 871 } 872 } 873 874 if (grammars.isEmpty()) 875 throw new BadCommandLineException( 876 Messages.format(Messages.MISSING_GRAMMAR)); 877 878 if (schemaLanguage == null) 879 schemaLanguage = guessSchemaLanguage(); 880 881 // if(target==SpecVersion.V2_2 && !isExtensionMode()) 882 // throw new BadCommandLineException( 883 // "Currently 2.2 is still not finalized yet, so using it requires the -extension switch." + 884 // "NOTE THAT 2.2 SPEC MAY CHANGE BEFORE IT BECOMES FINAL."); 885 886 if (pluginLoadFailure != null) 887 throw new BadCommandLineException( 888 Messages.format(Messages.PLUGIN_LOAD_FAILURE, pluginLoadFailure)); 889 } 890 891 /** 892 * Finds the {@code META-INF/sun-jaxb.episode} file to add as a binding customization. 893 * 894 * @param jar 895 * @throws com.sun.tools.internal.xjc.BadCommandLineException 896 */ 897 public void scanEpisodeFile(File jar) throws BadCommandLineException { 898 try { 899 URLClassLoader ucl = new URLClassLoader(new URL[]{jar.toURL()}); 900 Enumeration<URL> resources = ucl.findResources("META-INF/sun-jaxb.episode"); 901 while (resources.hasMoreElements()) { 902 URL url = resources.nextElement(); 903 addBindFile(new InputSource(url.toExternalForm())); 904 } 905 } catch (IOException e) { 906 throw new BadCommandLineException( 907 Messages.format(Messages.FAILED_TO_LOAD, jar, e.getMessage()), e); 908 } 909 } 910 911 912 /** 913 * Guesses the schema language. 914 * 915 * @return 916 */ 917 public Language guessSchemaLanguage() { 918 919 // otherwise, use the file extension. 920 // not a good solution, but very easy. 921 if ((grammars != null) && (grammars.size() > 0)) { 922 String name = grammars.get(0).getSystemId().toLowerCase(); 923 924 if (name.endsWith(".dtd")) 925 return Language.DTD; 926 if (name.endsWith(".wsdl")) 927 return Language.WSDL; 928 } 929 930 // by default, assume XML Schema 931 return Language.XMLSCHEMA; 932 } 933 934 /** 935 * Creates a configured CodeWriter that produces files into the specified directory. 936 * 937 * @return 938 * @throws java.io.IOException 939 */ 940 public CodeWriter createCodeWriter() throws IOException { 941 return createCodeWriter(new FileCodeWriter(targetDir, readOnly, encoding)); 942 } 943 944 /** 945 * Creates a configured CodeWriter that produces files into the specified directory. 946 * 947 * @param core 948 * @return 949 */ 950 public CodeWriter createCodeWriter(CodeWriter core) { 951 if (noFileHeader) 952 return core; 953 954 return new PrologCodeWriter(core, getPrologComment()); 955 } 956 957 /** 958 * Gets the string suitable to be used as the prolog comment baked into artifacts.This is the string like "This file was generated by the JAXB RI on YYYY/mm/dd..." 959 * 960 * @return 961 */ 962 public String getPrologComment() { 963 // generate format syntax: <date> 'at' <time> 964 String format = 965 Messages.format(Messages.DATE_FORMAT) 966 + " '" 967 + Messages.format(Messages.AT) 968 + "' " 969 + Messages.format(Messages.TIME_FORMAT); 970 SimpleDateFormat dateFormat = new SimpleDateFormat(format, Locale.ENGLISH); 971 972 return Messages.format( 973 Messages.FILE_PROLOG_COMMENT, 974 dateFormat.format(new Date())); 975 } 976 977 /** 978 * If a plugin failed to load, report. 979 */ 980 private String pluginLoadFailure; 981 982 /** 983 * Looks for all "META-INF/services/[className]" files and 984 * create one instance for each class name found inside this file. 985 */ 986 private <T> List<T> findServices(Class<T> clazz) { 987 final List<T> result = new ArrayList<>(); 988 final boolean debug = getDebugPropertyValue(); 989 try { 990 // TCCL allows user plugins to be loaded even if xjc is in jdk 991 // We have to use our SecureLoader to obtain it because we are trying to avoid SecurityException 992 final ClassLoader tccl = SecureLoader.getContextClassLoader(); 993 final ServiceLoader<T> sl = ServiceLoader.load(clazz, tccl); 994 for (T t : sl) 995 result.add(t); 996 } catch (Throwable e) { 997 // ignore any error 998 StringWriter w = new StringWriter(); 999 e.printStackTrace(new PrintWriter(w)); 1000 pluginLoadFailure = w.toString(); 1001 if (debug) 1002 System.out.println(pluginLoadFailure); 1003 } 1004 return result; 1005 } 1006 1007 private static boolean getDebugPropertyValue() { 1008 final String debugPropertyName = Options.class.getName() + ".findServices"; 1009 if (System.getSecurityManager() != null) { 1010 return AccessController.doPrivileged(new PrivilegedAction<Boolean>() { 1011 @Override 1012 public Boolean run() { 1013 return Boolean.getBoolean(debugPropertyName); 1014 } 1015 }); 1016 } else { 1017 return Boolean.getBoolean(debugPropertyName); 1018 } 1019 } 1020 1021 // this is a convenient place to expose the build version to xjc plugins 1022 public static String getBuildID() { 1023 return Messages.format(Messages.BUILD_ID); 1024 } 1025 1026 public static String normalizeSystemId(String systemId) { 1027 try { 1028 systemId = new URI(systemId).normalize().toString(); 1029 } catch (URISyntaxException e) { 1030 // leave the system ID untouched. In my experience URI is often too strict 1031 } 1032 return systemId; 1033 } 1034 1035 }