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