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