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