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.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.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 = findServices(Plugin.class);
 358         }
 359 
 360         return allPlugins;
 361     }
 362 
 363     public Language getSchemaLanguage() {
 364         if( schemaLanguage==null)
 365             schemaLanguage = guessSchemaLanguage();
 366         return schemaLanguage;
 367     }
 368     public void setSchemaLanguage(Language _schemaLanguage) {
 369         this.schemaLanguage = _schemaLanguage;
 370     }
 371 
 372     /** Input schema files. */
 373     public InputSource[] getGrammars() {
 374         return grammars.toArray(new InputSource[grammars.size()]);
 375     }
 376 
 377     /**
 378      * Adds a new input schema.
 379      */
 380     public void addGrammar( InputSource is ) {
 381         grammars.add(absolutize(is));
 382     }
 383 
 384     private InputSource fileToInputSource( File source ) {
 385         try {
 386             String url = source.toURL().toExternalForm();
 387             return new InputSource(Util.escapeSpace(url));
 388         } catch (MalformedURLException e) {
 389             return new InputSource(source.getPath());
 390         }
 391     }
 392 
 393     public void addGrammar( File source ) {
 394         addGrammar(fileToInputSource(source));
 395     }
 396 
 397     /**
 398      * Recursively scan directories and add all XSD files in it.
 399      */
 400     public void addGrammarRecursive( File dir ) {
 401         addRecursive(dir,".xsd",grammars);
 402     }
 403 
 404     private  void addRecursive( File dir, String suffix, List<InputSource> result ) {
 405         File[] files = dir.listFiles();
 406         if(files==null)     return; // work defensively
 407 
 408         for( File f : files ) {
 409             if(f.isDirectory())
 410                 addRecursive(f,suffix,result);
 411             else
 412             if(f.getPath().endsWith(suffix))
 413                 result.add(absolutize(fileToInputSource(f)));
 414         }
 415     }
 416 
 417 
 418     private InputSource absolutize(InputSource is) {
 419         // absolutize all the system IDs in the input, so that we can map system IDs to DOM trees.
 420         try {
 421             URL baseURL = new File(".").getCanonicalFile().toURL();
 422             is.setSystemId( new URL(baseURL,is.getSystemId()).toExternalForm() );
 423         } catch( IOException e ) {
 424             logger.log(Level.FINE, "{0}, {1}", new Object[]{is.getSystemId(), e.getLocalizedMessage()});
 425         }
 426         return is;
 427     }
 428 
 429     /** Input external binding files. */
 430     public InputSource[] getBindFiles() {
 431         return bindFiles.toArray(new InputSource[bindFiles.size()]);
 432     }
 433 
 434     /**
 435      * Adds a new binding file.
 436      */
 437     public void addBindFile( InputSource is ) {
 438         bindFiles.add(absolutize(is));
 439     }
 440 
 441     /**
 442      * Adds a new binding file.
 443      */
 444     public void addBindFile( File bindFile ) {
 445         bindFiles.add(fileToInputSource(bindFile));
 446     }
 447 
 448     /**
 449      * Recursively scan directories and add all ".xjb" files in it.
 450      */
 451     public void addBindFileRecursive( File dir ) {
 452         addRecursive(dir,".xjb",bindFiles);
 453     }
 454 
 455     public final List<URL> classpaths = new ArrayList<URL>();
 456     /**
 457      * Gets a classLoader that can load classes specified via the
 458      * -classpath option.
 459      */
 460     public ClassLoader getUserClassLoader( ClassLoader parent ) {
 461         if (classpaths.isEmpty())
 462             return parent;
 463         return new URLClassLoader(
 464                 classpaths.toArray(new URL[classpaths.size()]),parent);
 465     }
 466 
 467 
 468     /**
 469      * Parses an option <code>args[i]</code> and return
 470      * the number of tokens consumed.
 471      *
 472      * @return
 473      *      0 if the argument is not understood. Returning 0
 474      *      will let the caller report an error.
 475      * @exception BadCommandLineException
 476      *      If the callee wants to provide a custom message for an error.
 477      */
 478     public int parseArgument( String[] args, int i ) throws BadCommandLineException {
 479         if (args[i].equals("-classpath") || args[i].equals("-cp")) {
 480             String a = requireArgument(args[i], args, ++i);
 481             for (String p : a.split(File.pathSeparator)) {
 482                 File file = new File(p);
 483                 try {
 484                     classpaths.add(file.toURL());
 485                 } catch (MalformedURLException e) {
 486                     throw new BadCommandLineException(
 487                         Messages.format(Messages.NOT_A_VALID_FILENAME,file),e);
 488                 }
 489             }
 490             return 2;
 491         }
 492         if (args[i].equals("-d")) {
 493             targetDir = new File(requireArgument("-d",args,++i));
 494             if( !targetDir.exists() )
 495                 throw new BadCommandLineException(
 496                     Messages.format(Messages.NON_EXISTENT_DIR,targetDir));
 497             return 2;
 498         }
 499         if (args[i].equals("-readOnly")) {
 500             readOnly = true;
 501             return 1;
 502         }
 503         if (args[i].equals("-p")) {
 504             defaultPackage = requireArgument("-p",args,++i);
 505             if(defaultPackage.length()==0) { // user specified default package
 506                 // there won't be any package to annotate, so disable them
 507                 // automatically as a usability feature
 508                 packageLevelAnnotations = false;
 509             }
 510             return 2;
 511         }
 512         if (args[i].equals("-debug")) {
 513             debugMode = true;
 514             verbose = true;
 515             return 1;
 516         }
 517         if (args[i].equals("-nv")) {
 518             strictCheck = false;
 519             return 1;
 520         }
 521         if( args[i].equals("-npa")) {
 522             packageLevelAnnotations = false;
 523             return 1;
 524         }
 525         if( args[i].equals("-no-header")) {
 526             noFileHeader = true;
 527             return 1;
 528         }
 529         if (args[i].equals("-verbose")) {
 530             verbose = true;
 531             return 1;
 532         }
 533         if (args[i].equals("-quiet")) {
 534             quiet = true;
 535             return 1;
 536         }
 537         if (args[i].equals("-XexplicitAnnotation")) {
 538             runtime14 = true;
 539             return 1;
 540         }
 541         if (args[i].equals("-enableIntrospection")) {
 542             enableIntrospection = true;
 543             return 1;
 544         }
 545         if (args[i].equals("-disableXmlSecurity")) {
 546             disableXmlSecurity = true;
 547             return 1;
 548         }
 549         if (args[i].equals("-contentForWildcard")) {
 550             contentForWildcard = true;
 551             return 1;
 552         }
 553         if (args[i].equals("-XautoNameResolution")) {
 554             automaticNameConflictResolution = true;
 555             return 1;
 556         }
 557         if (args[i].equals("-b")) {
 558             addFile(requireArgument("-b",args,++i),bindFiles,".xjb");
 559             return 2;
 560         }
 561         if (args[i].equals("-dtd")) {
 562             schemaLanguage = Language.DTD;
 563             return 1;
 564         }
 565         if (args[i].equals("-relaxng")) {
 566             schemaLanguage = Language.RELAXNG;
 567             return 1;
 568         }
 569         if (args[i].equals("-relaxng-compact")) {
 570             schemaLanguage = Language.RELAXNG_COMPACT;
 571             return 1;
 572         }
 573         if (args[i].equals("-xmlschema")) {
 574             schemaLanguage = Language.XMLSCHEMA;
 575             return 1;
 576         }
 577         if (args[i].equals("-wsdl")) {
 578             schemaLanguage = Language.WSDL;
 579             return 1;
 580         }
 581         if (args[i].equals("-extension")) {
 582             compatibilityMode = EXTENSION;
 583             return 1;
 584         }
 585         if (args[i].equals("-target")) {
 586             String token = requireArgument("-target",args,++i);
 587             target = SpecVersion.parse(token);
 588             if(target==null)
 589                 throw new BadCommandLineException(Messages.format(Messages.ILLEGAL_TARGET_VERSION,token));
 590             return 2;
 591         }
 592         if (args[i].equals("-httpproxyfile")) {
 593             if (i == args.length - 1 || args[i + 1].startsWith("-")) {
 594                 throw new BadCommandLineException(
 595                     Messages.format(Messages.MISSING_PROXYFILE));
 596             }
 597 
 598             File file = new File(args[++i]);
 599             if(!file.exists()) {
 600                 throw new BadCommandLineException(
 601                     Messages.format(Messages.NO_SUCH_FILE,file));
 602             }
 603 
 604             try {
 605                 BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file),"UTF-8"));
 606                 parseProxy(in.readLine());
 607                 in.close();
 608             } catch (IOException e) {
 609                 throw new BadCommandLineException(
 610                     Messages.format(Messages.FAILED_TO_PARSE,file,e.getMessage()),e);
 611             }
 612 
 613             return 2;
 614         }
 615         if (args[i].equals("-httpproxy")) {
 616             if (i == args.length - 1 || args[i + 1].startsWith("-")) {
 617                 throw new BadCommandLineException(
 618                     Messages.format(Messages.MISSING_PROXY));
 619             }
 620 
 621             parseProxy(args[++i]);
 622             return 2;
 623         }
 624         if (args[i].equals("-host")) {
 625             proxyHost = requireArgument("-host",args,++i);
 626             return 2;
 627         }
 628         if (args[i].equals("-port")) {
 629             proxyPort = requireArgument("-port",args,++i);
 630             return 2;
 631         }
 632         if( args[i].equals("-catalog") ) {
 633             // use Sun's "XML Entity and URI Resolvers" by Norman Walsh
 634             // to resolve external entities.
 635             // http://www.sun.com/xml/developers/resolver/
 636 
 637             File catalogFile = new File(requireArgument("-catalog",args,++i));
 638             try {
 639                 addCatalog(catalogFile);
 640             } catch (IOException e) {
 641                 throw new BadCommandLineException(
 642                     Messages.format(Messages.FAILED_TO_PARSE,catalogFile,e.getMessage()),e);
 643             }
 644             return 2;
 645         }
 646         if( args[i].equals("-Xtest-class-name-allocator") ) {
 647             classNameAllocator = new ClassNameAllocator() {
 648                 public String assignClassName(String packageName, String className) {
 649                     System.out.printf("assignClassName(%s,%s)\n",packageName,className);
 650                     return className+"_Type";
 651                 }
 652             };
 653             return 1;
 654         }
 655 
 656         if (args[i].equals("-encoding")) {
 657             encoding = requireArgument("-encoding", args, ++i);
 658             try {
 659                 if (!Charset.isSupported(encoding)) {
 660                     throw new BadCommandLineException(
 661                         Messages.format(Messages.UNSUPPORTED_ENCODING, encoding));
 662                 }
 663             } catch (IllegalCharsetNameException icne) {
 664                 throw new BadCommandLineException(
 665                     Messages.format(Messages.UNSUPPORTED_ENCODING, encoding));
 666             }
 667             return 2;
 668         }
 669 
 670         // see if this is one of the extensions
 671         for( Plugin plugin : getAllPlugins() ) {
 672             try {
 673                 if( ('-'+plugin.getOptionName()).equals(args[i]) ) {
 674                     activePlugins.add(plugin);
 675                     plugin.onActivated(this);
 676                     pluginURIs.addAll(plugin.getCustomizationURIs());
 677 
 678                     // give the plugin a chance to parse arguments to this option.
 679                     // this is new in 2.1, and due to the backward compatibility reason,
 680                     // if plugin didn't understand it, we still return 1 to indicate
 681                     // that this option is consumed.
 682                     int r = plugin.parseArgument(this,args,i);
 683                     if(r!=0)
 684                         return r;
 685                     else
 686                         return 1;
 687                 }
 688 
 689                 int r = plugin.parseArgument(this,args,i);
 690                 if(r!=0)    return r;
 691             } catch (IOException e) {
 692                 throw new BadCommandLineException(e.getMessage(),e);
 693             }
 694         }
 695 
 696         return 0;   // unrecognized
 697     }
 698 
 699     private void parseProxy(String text) throws BadCommandLineException {
 700         int i = text.lastIndexOf('@');
 701         int j = text.lastIndexOf(':');
 702 
 703         if (i > 0) {
 704             proxyAuth = text.substring(0, i);
 705             if (j > i) {
 706                 proxyHost = text.substring(i + 1, j);
 707                 proxyPort = text.substring(j + 1);
 708             } else {
 709                 proxyHost = text.substring(i + 1);
 710                 proxyPort = "80";
 711             }
 712         } else {
 713             //no auth info
 714             if (j < 0) {
 715                 //no port
 716                 proxyHost = text;
 717                 proxyPort = "80";
 718             } else {
 719                 proxyHost = text.substring(0, j);
 720                 proxyPort = text.substring(j + 1);
 721             }
 722         }
 723         try {
 724             Integer.valueOf(proxyPort);
 725         } catch (NumberFormatException e) {
 726             throw new BadCommandLineException(Messages.format(Messages.ILLEGAL_PROXY,text));
 727         }
 728     }
 729 
 730     /**
 731      * Obtains an operand and reports an error if it's not there.
 732      */
 733     public String requireArgument(String optionName, String[] args, int i) throws BadCommandLineException {
 734         if (i == args.length || args[i].startsWith("-")) {
 735             throw new BadCommandLineException(
 736                 Messages.format(Messages.MISSING_OPERAND,optionName));
 737         }
 738         return args[i];
 739     }
 740 
 741     /**
 742      * Parses a token to a file (or a set of files)
 743      * and add them as {@link InputSource} to the specified list.
 744      *
 745      * @param suffix
 746      *      If the given token is a directory name, we do a recusive search
 747      *      and find all files that have the given suffix.
 748      */
 749     private void addFile(String name, List<InputSource> target, String suffix) throws BadCommandLineException {
 750         Object src;
 751         try {
 752             src = Util.getFileOrURL(name);
 753         } catch (IOException e) {
 754             throw new BadCommandLineException(
 755                 Messages.format(Messages.NOT_A_FILE_NOR_URL,name));
 756         }
 757         if(src instanceof URL) {
 758             target.add(absolutize(new InputSource(Util.escapeSpace(((URL)src).toExternalForm()))));
 759         } else {
 760             File fsrc = (File)src;
 761             if(fsrc.isDirectory()) {
 762                 addRecursive(fsrc,suffix,target);
 763             } else {
 764                 target.add(absolutize(fileToInputSource(fsrc)));
 765             }
 766         }
 767     }
 768 
 769     /**
 770      * Adds a new catalog file.
 771      */
 772     public void addCatalog(File catalogFile) throws IOException {
 773         if(entityResolver==null) {
 774             final CatalogManager staticManager = CatalogManager.getStaticManager();
 775             // hack to force initialization so catalog manager system properties take effect
 776             staticManager.getVerbosity();
 777             staticManager.setIgnoreMissingProperties(true);
 778             entityResolver = new CatalogResolver(true);
 779         }
 780         ((CatalogResolver)entityResolver).getCatalog().parseCatalog(catalogFile.getPath());
 781     }
 782 
 783     /**
 784      * Parses arguments and fill fields of this object.
 785      *
 786      * @exception BadCommandLineException
 787      *      thrown when there's a problem in the command-line arguments
 788      */
 789     public void parseArguments( String[] args ) throws BadCommandLineException {
 790 
 791         for (int i = 0; i < args.length; i++) {
 792             if(args[i].length()==0)
 793                 throw new BadCommandLineException();
 794             if (args[i].charAt(0) == '-') {
 795                 int j = parseArgument(args,i);
 796                 if(j==0)
 797                     throw new BadCommandLineException(
 798                         Messages.format(Messages.UNRECOGNIZED_PARAMETER, args[i]));
 799                 i += (j-1);
 800             } else {
 801                 if(args[i].endsWith(".jar"))
 802                     scanEpisodeFile(new File(args[i]));
 803                 else
 804                     addFile(args[i],grammars,".xsd");
 805             }
 806         }
 807 
 808         // configure proxy
 809         if (proxyHost != null || proxyPort != null) {
 810             if (proxyHost != null && proxyPort != null) {
 811                 System.setProperty("http.proxyHost", proxyHost);
 812                 System.setProperty("http.proxyPort", proxyPort);
 813                 System.setProperty("https.proxyHost", proxyHost);
 814                 System.setProperty("https.proxyPort", proxyPort);
 815             } else if (proxyHost == null) {
 816                 throw new BadCommandLineException(
 817                     Messages.format(Messages.MISSING_PROXYHOST));
 818             } else {
 819                 throw new BadCommandLineException(
 820                     Messages.format(Messages.MISSING_PROXYPORT));
 821             }
 822             if (proxyAuth != null) {
 823                 DefaultAuthenticator.getAuthenticator().setProxyAuth(proxyAuth);
 824             }
 825         }
 826 
 827         if (grammars.isEmpty())
 828             throw new BadCommandLineException(
 829                 Messages.format(Messages.MISSING_GRAMMAR));
 830 
 831         if( schemaLanguage==null )
 832             schemaLanguage = guessSchemaLanguage();
 833 
 834 //        if(target==SpecVersion.V2_2 && !isExtensionMode())
 835 //            throw new BadCommandLineException(
 836 //                "Currently 2.2 is still not finalized yet, so using it requires the -extension switch." +
 837 //                "NOTE THAT 2.2 SPEC MAY CHANGE BEFORE IT BECOMES FINAL.");
 838 
 839         if(pluginLoadFailure!=null)
 840             throw new BadCommandLineException(
 841                 Messages.format(Messages.PLUGIN_LOAD_FAILURE,pluginLoadFailure));
 842     }
 843 
 844     /**
 845      * Finds the <tt>META-INF/sun-jaxb.episode</tt> file to add as a binding customization.
 846      */
 847     public void scanEpisodeFile(File jar) throws BadCommandLineException {
 848         try {
 849             URLClassLoader ucl = new URLClassLoader(new URL[]{jar.toURL()});
 850             Enumeration<URL> resources = ucl.findResources("META-INF/sun-jaxb.episode");
 851             while (resources.hasMoreElements()) {
 852                 URL url = resources.nextElement();
 853                 addBindFile(new InputSource(url.toExternalForm()));
 854             }
 855         } catch (IOException e) {
 856             throw new BadCommandLineException(
 857                     Messages.format(Messages.FAILED_TO_LOAD,jar,e.getMessage()), e);
 858         }
 859     }
 860 
 861 
 862     /**
 863      * Guesses the schema language.
 864      */
 865     public Language guessSchemaLanguage() {
 866 
 867         // otherwise, use the file extension.
 868         // not a good solution, but very easy.
 869         if ((grammars != null) && (grammars.size() > 0)) {
 870             String name = grammars.get(0).getSystemId().toLowerCase();
 871 
 872             if (name.endsWith(".rng"))
 873                 return Language.RELAXNG;
 874             if (name.endsWith(".rnc"))
 875                 return Language.RELAXNG_COMPACT;
 876             if (name.endsWith(".dtd"))
 877                 return Language.DTD;
 878             if (name.endsWith(".wsdl"))
 879                 return Language.WSDL;
 880         }
 881 
 882         // by default, assume XML Schema
 883         return Language.XMLSCHEMA;
 884     }
 885 
 886     /**
 887      * Creates a configured CodeWriter that produces files into the specified directory.
 888      */
 889     public CodeWriter createCodeWriter() throws IOException {
 890         return createCodeWriter(new FileCodeWriter( targetDir, readOnly, encoding ));
 891     }
 892 
 893     /**
 894      * Creates a configured CodeWriter that produces files into the specified directory.
 895      */
 896     public CodeWriter createCodeWriter( CodeWriter core ) {
 897         if(noFileHeader)
 898             return core;
 899 
 900         return new PrologCodeWriter( core,getPrologComment() );
 901     }
 902 
 903     /**
 904      * Gets the string suitable to be used as the prolog comment baked into artifacts.
 905      * This is the string like "This file was generated by the JAXB RI on YYYY/mm/dd..."
 906      */
 907     public String getPrologComment() {
 908         // generate format syntax: <date> 'at' <time>
 909         String format =
 910             Messages.format(Messages.DATE_FORMAT)
 911                 + " '"
 912                 + Messages.format(Messages.AT)
 913                 + "' "
 914                 + Messages.format(Messages.TIME_FORMAT);
 915         SimpleDateFormat dateFormat = new SimpleDateFormat(format, Locale.ENGLISH);
 916 
 917         return Messages.format(
 918             Messages.FILE_PROLOG_COMMENT,
 919             dateFormat.format(new Date()));
 920     }
 921 
 922     /**
 923      * If a plugin failed to load, report.
 924      */
 925     private String pluginLoadFailure;
 926 
 927     /**
 928      * Looks for all "META-INF/services/[className]" files and
 929      * create one instance for each class name found inside this file.
 930      */
 931     private <T> List<T> findServices( Class<T> clazz) {
 932         final List<T> result = new ArrayList<T>();
 933         final boolean debug = getDebugPropertyValue();
 934         try {
 935             // TCCL allows user plugins to be loaded even if xjc is in jdk
 936             // We have to use our SecureLoader to obtain it because we are trying to avoid SecurityException
 937             final ClassLoader tccl = SecureLoader.getContextClassLoader();
 938             final ServiceLoader<T> sl = ServiceLoader.load(clazz, tccl);
 939             for (T t : sl)
 940                 result.add(t);
 941         } catch( Throwable e ) {
 942             // ignore any error
 943             StringWriter w = new StringWriter();
 944             e.printStackTrace(new PrintWriter(w));
 945             pluginLoadFailure = w.toString();
 946             if(debug)
 947                 System.out.println(pluginLoadFailure);
 948         }
 949         return result;
 950     }
 951 
 952     private static boolean getDebugPropertyValue() {
 953         final String debugPropertyName = Options.class.getName() + ".findServices";
 954         if (System.getSecurityManager() != null) {
 955             return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
 956                 @Override
 957                 public Boolean run() {
 958                     return Boolean.getBoolean(debugPropertyName);
 959                 }
 960             });
 961         } else {
 962             return Boolean.getBoolean(debugPropertyName);
 963         }
 964     }
 965 
 966     // this is a convenient place to expose the build version to xjc plugins
 967     public static String getBuildID() {
 968         return Messages.format(Messages.BUILD_ID);
 969     }
 970 
 971     public static String normalizeSystemId(String systemId) {
 972         try {
 973             systemId = new URI(systemId).normalize().toString();
 974         } catch (URISyntaxException e) {
 975             // leave the system ID untouched. In my experience URI is often too strict
 976         }
 977         return systemId;
 978     }
 979 
 980 }