1 /*
   2  * Copyright (c) 1997, 2013, 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 import java.util.regex.Matcher;
  49 import java.util.regex.Pattern;
  50 
  51 import com.sun.codemodel.internal.CodeWriter;
  52 import com.sun.codemodel.internal.JPackage;
  53 import com.sun.codemodel.internal.writer.FileCodeWriter;
  54 import com.sun.codemodel.internal.writer.PrologCodeWriter;
  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.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</code>
 147      * or <code>EXTENSION</code>.
 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     private boolean is2_2 = true;
 163 
 164     public Options() {
 165         if (is2_2) {
 166             try {
 167                 Class.forName("javax.xml.bind.JAXBPermission");
 168             } catch (ClassNotFoundException cnfe) {
 169                 is2_2 = false;
 170             }
 171             if (!is2_2) {
 172                 target = SpecVersion.V2_1;
 173             } else {
 174                 target = SpecVersion.LATEST;
 175             }
 176         }
 177     }
 178 
 179     /**
 180      * Target directory when producing files.
 181      * <p>
 182      * This field is not used when XJC is driven through the XJC API.
 183      * Plugins that need to generate extra files should do so by using
 184      * {@link JPackage#addResourceFile(JResourceFile)}.
 185      */
 186     public File targetDir = new File(".");
 187 
 188     /**
 189      * Actually stores {@link CatalogResolver}, but the field
 190      * type is made to {@link EntityResolver} so that XJC can be
 191      * used even if resolver.jar is not available in the classpath.
 192      */
 193     public EntityResolver entityResolver = null;
 194 
 195     /**
 196      * Type of input schema language. One of the <code>SCHEMA_XXX</code>
 197      * constants.
 198      */
 199     private Language schemaLanguage = null;
 200 
 201     /**
 202      * The -p option that should control the default Java package that
 203      * will contain the generated code. Null if unspecified.
 204      */
 205     public String defaultPackage = null;
 206 
 207     /**
 208      * Similar to the -p option, but this one works with a lower priority,
 209      * and customizations overrides this. Used by JAX-RPC.
 210      */
 211     public String defaultPackage2 = null;
 212 
 213     /**
 214      * Input schema files as a list of {@link InputSource}s.
 215      */
 216     private final List<InputSource> grammars = new ArrayList<InputSource>();
 217 
 218     private final List<InputSource> bindFiles = new ArrayList<InputSource>();
 219 
 220     // Proxy setting.
 221     private String proxyHost = null;
 222     private String proxyPort = null;
 223     private String proxyUser = null;
 224     private String proxyPassword = null;
 225 
 226     /**
 227      * {@link Plugin}s that are enabled in this compilation.
 228      */
 229     public final List<Plugin> activePlugins = new ArrayList<Plugin>();
 230 
 231     /**
 232      * All discovered {@link Plugin}s.
 233      * This is lazily parsed, so that we can take '-cp' option into account.
 234      *
 235      * @see #getAllPlugins()
 236      */
 237     private List<Plugin> allPlugins;
 238 
 239     /**
 240      * Set of URIs that plug-ins recognize as extension bindings.
 241      */
 242     public final Set<String> pluginURIs = new HashSet<String>();
 243 
 244     /**
 245      * This allocator has the final say on deciding the class name.
 246      */
 247     public ClassNameAllocator classNameAllocator;
 248 
 249     /**
 250      * This switch controls whether or not xjc will generate package level annotations
 251      */
 252     public boolean packageLevelAnnotations = true;
 253 
 254     /**
 255      * This {@link FieldRendererFactory} determines how the fields are generated.
 256      */
 257     private FieldRendererFactory fieldRendererFactory = new FieldRendererFactory();
 258     /**
 259      * Used to detect if two {@link Plugin}s try to overwrite {@link #fieldRendererFactory}.
 260      */
 261     private Plugin fieldRendererFactoryOwner = null;
 262 
 263     /**
 264      * If this is non-null, we use this {@link NameConverter} over the one
 265      * given in the schema/binding.
 266      */
 267     private NameConverter nameConverter = null;
 268     /**
 269      * Used to detect if two {@link Plugin}s try to overwrite {@link #nameConverter}.
 270      */
 271     private Plugin nameConverterOwner = null;
 272 
 273     /**
 274      * Gets the active {@link FieldRendererFactory} that shall be used to build {@link Model}.
 275      *
 276      * @return always non-null.
 277      */
 278     public FieldRendererFactory getFieldRendererFactory() {
 279         return fieldRendererFactory;
 280     }
 281 
 282     /**
 283      * Sets the {@link FieldRendererFactory}.
 284      *
 285      * <p>
 286      * This method is for plugins to call to set a custom {@link FieldRendererFactory}.
 287      *
 288      * @param frf
 289      *      The {@link FieldRendererFactory} to be installed. Must not be null.
 290      * @param owner
 291      *      Identifies the plugin that owns this {@link FieldRendererFactory}.
 292      *      When two {@link Plugin}s try to call this method, this allows XJC
 293      *      to report it as a user-friendly error message.
 294      *
 295      * @throws BadCommandLineException
 296      *      If a conflit happens, this exception carries a user-friendly error
 297      *      message, indicating a conflict.
 298      */
 299     public void setFieldRendererFactory(FieldRendererFactory frf, Plugin owner) throws BadCommandLineException {
 300         // since this method is for plugins, make it bit more fool-proof than usual
 301         if(frf==null)
 302             throw new IllegalArgumentException();
 303         if(fieldRendererFactoryOwner!=null) {
 304             throw new BadCommandLineException(
 305                 Messages.format(Messages.FIELD_RENDERER_CONFLICT,
 306                     fieldRendererFactoryOwner.getOptionName(),
 307                     owner.getOptionName() ));
 308         }
 309         this.fieldRendererFactoryOwner = owner;
 310         this.fieldRendererFactory = frf;
 311     }
 312 
 313 
 314     /**
 315      * Gets the active {@link NameConverter} that shall be used to build {@link Model}.
 316      *
 317      * @return can be null, in which case it's up to the binding.
 318      */
 319     public NameConverter getNameConverter() {
 320         return nameConverter;
 321     }
 322 
 323     /**
 324      * Sets the {@link NameConverter}.
 325      *
 326      * <p>
 327      * This method is for plugins to call to set a custom {@link NameConverter}.
 328      *
 329      * @param nc
 330      *      The {@link NameConverter} to be installed. Must not be null.
 331      * @param owner
 332      *      Identifies the plugin that owns this {@link NameConverter}.
 333      *      When two {@link Plugin}s try to call this method, this allows XJC
 334      *      to report it as a user-friendly error message.
 335      *
 336      * @throws BadCommandLineException
 337      *      If a conflit happens, this exception carries a user-friendly error
 338      *      message, indicating a conflict.
 339      */
 340     public void setNameConverter(NameConverter nc, Plugin owner) throws BadCommandLineException {
 341         // since this method is for plugins, make it bit more fool-proof than usual
 342         if(nc==null)
 343             throw new IllegalArgumentException();
 344         if(nameConverter!=null) {
 345             throw new BadCommandLineException(
 346                 Messages.format(Messages.NAME_CONVERTER_CONFLICT,
 347                     nameConverterOwner.getOptionName(),
 348                     owner.getOptionName() ));
 349         }
 350         this.nameConverterOwner = owner;
 351         this.nameConverter = nc;
 352     }
 353 
 354     /**
 355      * Gets all the {@link Plugin}s discovered so far.
 356      *
 357      * <p>
 358      * A plugins are enumerated when this method is called for the first time,
 359      * by taking {@link #classpaths} into account. That means
 360      * "-cp plugin.jar" has to come before you specify options to enable it.
 361      */
 362     public List<Plugin> getAllPlugins() {
 363         if(allPlugins==null) {
 364             allPlugins = new ArrayList<Plugin>();
 365             ClassLoader ucl = getUserClassLoader(getClass().getClassLoader());
 366             allPlugins.addAll(Arrays.asList(findServices(Plugin.class,ucl)));
 367         }
 368 
 369         return allPlugins;
 370     }
 371 
 372     public Language getSchemaLanguage() {
 373         if( schemaLanguage==null)
 374             schemaLanguage = guessSchemaLanguage();
 375         return schemaLanguage;
 376     }
 377     public void setSchemaLanguage(Language _schemaLanguage) {
 378         this.schemaLanguage = _schemaLanguage;
 379     }
 380 
 381     /** Input schema files. */
 382     public InputSource[] getGrammars() {
 383         return grammars.toArray(new InputSource[grammars.size()]);
 384     }
 385 
 386     /**
 387      * Adds a new input schema.
 388      */
 389     public void addGrammar( InputSource is ) {
 390         grammars.add(absolutize(is));
 391     }
 392 
 393     private InputSource fileToInputSource( File source ) {
 394         try {
 395             String url = source.toURL().toExternalForm();
 396             return new InputSource(Util.escapeSpace(url));
 397         } catch (MalformedURLException e) {
 398             return new InputSource(source.getPath());
 399         }
 400     }
 401 
 402     public void addGrammar( File source ) {
 403         addGrammar(fileToInputSource(source));
 404     }
 405 
 406     /**
 407      * Recursively scan directories and add all XSD files in it.
 408      */
 409     public void addGrammarRecursive( File dir ) {
 410         addRecursive(dir,".xsd",grammars);
 411     }
 412 
 413     private  void addRecursive( File dir, String suffix, List<InputSource> result ) {
 414         File[] files = dir.listFiles();
 415         if(files==null)     return; // work defensively
 416 
 417         for( File f : files ) {
 418             if(f.isDirectory())
 419                 addRecursive(f,suffix,result);
 420             else
 421             if(f.getPath().endsWith(suffix))
 422                 result.add(absolutize(fileToInputSource(f)));
 423         }
 424     }
 425 
 426 
 427     private InputSource absolutize(InputSource is) {
 428         // absolutize all the system IDs in the input, so that we can map system IDs to DOM trees.
 429         try {
 430             URL baseURL = new File(".").getCanonicalFile().toURL();
 431             is.setSystemId( new URL(baseURL,is.getSystemId()).toExternalForm() );
 432         } catch( IOException e ) {
 433             logger.log(Level.FINE, "{0}, {1}", new Object[]{is.getSystemId(), e.getLocalizedMessage()});
 434         }
 435         return is;
 436     }
 437 
 438     /** Input external binding files. */
 439     public InputSource[] getBindFiles() {
 440         return bindFiles.toArray(new InputSource[bindFiles.size()]);
 441     }
 442 
 443     /**
 444      * Adds a new binding file.
 445      */
 446     public void addBindFile( InputSource is ) {
 447         bindFiles.add(absolutize(is));
 448     }
 449 
 450     /**
 451      * Adds a new binding file.
 452      */
 453     public void addBindFile( File bindFile ) {
 454         bindFiles.add(fileToInputSource(bindFile));
 455     }
 456 
 457     /**
 458      * Recursively scan directories and add all ".xjb" files in it.
 459      */
 460     public void addBindFileRecursive( File dir ) {
 461         addRecursive(dir,".xjb",bindFiles);
 462     }
 463 
 464     public final List<URL> classpaths = new ArrayList<URL>();
 465     /**
 466      * Gets a classLoader that can load classes specified via the
 467      * -classpath option.
 468      */
 469     public URLClassLoader getUserClassLoader( ClassLoader parent ) {
 470         return new URLClassLoader(
 471                 classpaths.toArray(new URL[classpaths.size()]),parent);
 472     }
 473 
 474 
 475     /**
 476      * Parses an option <code>args[i]</code> and return
 477      * the number of tokens consumed.
 478      *
 479      * @return
 480      *      0 if the argument is not understood. Returning 0
 481      *      will let the caller report an error.
 482      * @exception BadCommandLineException
 483      *      If the callee wants to provide a custom message for an error.
 484      */
 485     public int parseArgument( String[] args, int i ) throws BadCommandLineException {
 486         if (args[i].equals("-classpath") || args[i].equals("-cp")) {
 487             String a = requireArgument(args[i], args, ++i);
 488             for (String p : a.split(File.pathSeparator)) {
 489                 File file = new File(p);
 490                 try {
 491                     classpaths.add(file.toURL());
 492                 } catch (MalformedURLException e) {
 493                     throw new BadCommandLineException(
 494                         Messages.format(Messages.NOT_A_VALID_FILENAME,file),e);
 495                 }
 496             }
 497             return 2;
 498         }
 499         if (args[i].equals("-d")) {
 500             targetDir = new File(requireArgument("-d",args,++i));
 501             if( !targetDir.exists() )
 502                 throw new BadCommandLineException(
 503                     Messages.format(Messages.NON_EXISTENT_DIR,targetDir));
 504             return 2;
 505         }
 506         if (args[i].equals("-readOnly")) {
 507             readOnly = true;
 508             return 1;
 509         }
 510         if (args[i].equals("-p")) {
 511             defaultPackage = requireArgument("-p",args,++i);
 512             if(defaultPackage.length()==0) { // user specified default package
 513                 // there won't be any package to annotate, so disable them
 514                 // automatically as a usability feature
 515                 packageLevelAnnotations = false;
 516             }
 517             return 2;
 518         }
 519         if (args[i].equals("-debug")) {
 520             debugMode = true;
 521             verbose = true;
 522             return 1;
 523         }
 524         if (args[i].equals("-nv")) {
 525             strictCheck = false;
 526             return 1;
 527         }
 528         if( args[i].equals("-npa")) {
 529             packageLevelAnnotations = false;
 530             return 1;
 531         }
 532         if( args[i].equals("-no-header")) {
 533             noFileHeader = true;
 534             return 1;
 535         }
 536         if (args[i].equals("-verbose")) {
 537             verbose = true;
 538             return 1;
 539         }
 540         if (args[i].equals("-quiet")) {
 541             quiet = true;
 542             return 1;
 543         }
 544         if (args[i].equals("-XexplicitAnnotation")) {
 545             runtime14 = true;
 546             return 1;
 547         }
 548         if (args[i].equals("-enableIntrospection")) {
 549             enableIntrospection = true;
 550             return 1;
 551         }
 552         if (args[i].equals("-disableXmlSecurity")) {
 553             disableXmlSecurity = true;
 554             return 1;
 555         }
 556         if (args[i].equals("-contentForWildcard")) {
 557             contentForWildcard = true;
 558             return 1;
 559         }
 560         if (args[i].equals("-XautoNameResolution")) {
 561             automaticNameConflictResolution = true;
 562             return 1;
 563         }
 564         if (args[i].equals("-b")) {
 565             addFile(requireArgument("-b",args,++i),bindFiles,".xjb");
 566             return 2;
 567         }
 568         if (args[i].equals("-dtd")) {
 569             schemaLanguage = Language.DTD;
 570             return 1;
 571         }
 572         if (args[i].equals("-relaxng")) {
 573             schemaLanguage = Language.RELAXNG;
 574             return 1;
 575         }
 576         if (args[i].equals("-relaxng-compact")) {
 577             schemaLanguage = Language.RELAXNG_COMPACT;
 578             return 1;
 579         }
 580         if (args[i].equals("-xmlschema")) {
 581             schemaLanguage = Language.XMLSCHEMA;
 582             return 1;
 583         }
 584         if (args[i].equals("-wsdl")) {
 585             schemaLanguage = Language.WSDL;
 586             return 1;
 587         }
 588         if (args[i].equals("-extension")) {
 589             compatibilityMode = EXTENSION;
 590             return 1;
 591         }
 592         if (args[i].equals("-target")) {
 593             String token = requireArgument("-target",args,++i);
 594             target = SpecVersion.parse(token);
 595             if(target==null)
 596                 throw new BadCommandLineException(Messages.format(Messages.ILLEGAL_TARGET_VERSION,token));
 597             return 2;
 598         }
 599         if (args[i].equals("-httpproxyfile")) {
 600             if (i == args.length - 1 || args[i + 1].startsWith("-")) {
 601                 throw new BadCommandLineException(
 602                     Messages.format(Messages.MISSING_PROXYFILE));
 603             }
 604 
 605             File file = new File(args[++i]);
 606             if(!file.exists()) {
 607                 throw new BadCommandLineException(
 608                     Messages.format(Messages.NO_SUCH_FILE,file));
 609             }
 610 
 611             try {
 612                 BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file),"UTF-8"));
 613                 parseProxy(in.readLine());
 614                 in.close();
 615             } catch (IOException e) {
 616                 throw new BadCommandLineException(
 617                     Messages.format(Messages.FAILED_TO_PARSE,file,e.getMessage()),e);
 618             }
 619 
 620             return 2;
 621         }
 622         if (args[i].equals("-httpproxy")) {
 623             if (i == args.length - 1 || args[i + 1].startsWith("-")) {
 624                 throw new BadCommandLineException(
 625                     Messages.format(Messages.MISSING_PROXY));
 626             }
 627 
 628             parseProxy(args[++i]);
 629             return 2;
 630         }
 631         if (args[i].equals("-host")) {
 632             proxyHost = requireArgument("-host",args,++i);
 633             return 2;
 634         }
 635         if (args[i].equals("-port")) {
 636             proxyPort = requireArgument("-port",args,++i);
 637             return 2;
 638         }
 639         if( args[i].equals("-catalog") ) {
 640             // use Sun's "XML Entity and URI Resolvers" by Norman Walsh
 641             // to resolve external entities.
 642             // http://www.sun.com/xml/developers/resolver/
 643 
 644             File catalogFile = new File(requireArgument("-catalog",args,++i));
 645             try {
 646                 addCatalog(catalogFile);
 647             } catch (IOException e) {
 648                 throw new BadCommandLineException(
 649                     Messages.format(Messages.FAILED_TO_PARSE,catalogFile,e.getMessage()),e);
 650             }
 651             return 2;
 652         }
 653         if (args[i].equals("-source")) {
 654             String version = requireArgument("-source",args,++i);
 655             //For source 1.0 the 1.0 Driver is loaded
 656             //Hence anything other than 2.0 is defaulted to
 657             //2.0
 658             if( !version.equals("2.0") && !version.equals("2.1") )
 659                 throw new BadCommandLineException(
 660                     Messages.format(Messages.DEFAULT_VERSION));
 661             return 2;
 662         }
 663         if( args[i].equals("-Xtest-class-name-allocator") ) {
 664             classNameAllocator = new ClassNameAllocator() {
 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         // syntax is [user[:password]@]proxyHost:proxyPort
 718         String token = "([^@:]+)";
 719         Pattern p = Pattern.compile("(?:"+token+"(?:\\:"+token+")?\\@)?"+token+"(?:\\:"+token+")");
 720 
 721         Matcher matcher = p.matcher(text);
 722         if(!matcher.matches())
 723             throw new BadCommandLineException(Messages.format(Messages.ILLEGAL_PROXY,text));
 724 
 725         proxyUser = matcher.group(1);
 726         proxyPassword = matcher.group(2);
 727         proxyHost = matcher.group(3);
 728         proxyPort = matcher.group(4);
 729         try {
 730             Integer.valueOf(proxyPort);
 731         } catch (NumberFormatException e) {
 732             throw new BadCommandLineException(Messages.format(Messages.ILLEGAL_PROXY,text));
 733         }
 734     }
 735 
 736     /**
 737      * Obtains an operand and reports an error if it's not there.
 738      */
 739     public String requireArgument(String optionName, String[] args, int i) throws BadCommandLineException {
 740         if (i == args.length || args[i].startsWith("-")) {
 741             throw new BadCommandLineException(
 742                 Messages.format(Messages.MISSING_OPERAND,optionName));
 743         }
 744         return args[i];
 745     }
 746 
 747     /**
 748      * Parses a token to a file (or a set of files)
 749      * and add them as {@link InputSource} to the specified list.
 750      *
 751      * @param suffix
 752      *      If the given token is a directory name, we do a recusive search
 753      *      and find all files that have the given suffix.
 754      */
 755     private void addFile(String name, List<InputSource> target, String suffix) throws BadCommandLineException {
 756         Object src;
 757         try {
 758             src = Util.getFileOrURL(name);
 759         } catch (IOException e) {
 760             throw new BadCommandLineException(
 761                 Messages.format(Messages.NOT_A_FILE_NOR_URL,name));
 762         }
 763         if(src instanceof URL) {
 764             target.add(absolutize(new InputSource(Util.escapeSpace(((URL)src).toExternalForm()))));
 765         } else {
 766             File fsrc = (File)src;
 767             if(fsrc.isDirectory()) {
 768                 addRecursive(fsrc,suffix,target);
 769             } else {
 770                 target.add(absolutize(fileToInputSource(fsrc)));
 771             }
 772         }
 773     }
 774 
 775     /**
 776      * Adds a new catalog file.
 777      */
 778     public void addCatalog(File catalogFile) throws IOException {
 779         if(entityResolver==null) {
 780             CatalogManager.getStaticManager().setIgnoreMissingProperties(true);
 781             entityResolver = new CatalogResolver(true);
 782         }
 783         ((CatalogResolver)entityResolver).getCatalog().parseCatalog(catalogFile.getPath());
 784     }
 785 
 786     /**
 787      * Parses arguments and fill fields of this object.
 788      *
 789      * @exception BadCommandLineException
 790      *      thrown when there's a problem in the command-line arguments
 791      */
 792     public void parseArguments( String[] args ) throws BadCommandLineException {
 793 
 794         for (int i = 0; i < args.length; i++) {
 795             if(args[i].length()==0)
 796                 throw new BadCommandLineException();
 797             if (args[i].charAt(0) == '-') {
 798                 int j = parseArgument(args,i);
 799                 if(j==0)
 800                     throw new BadCommandLineException(
 801                         Messages.format(Messages.UNRECOGNIZED_PARAMETER, args[i]));
 802                 i += (j-1);
 803             } else {
 804                 if(args[i].endsWith(".jar"))
 805                     scanEpisodeFile(new File(args[i]));
 806                 else
 807                     addFile(args[i],grammars,".xsd");
 808             }
 809         }
 810 
 811         // configure proxy
 812         if (proxyHost != null || proxyPort != null) {
 813             if (proxyHost != null && proxyPort != null) {
 814                 System.setProperty("http.proxyHost", proxyHost);
 815                 System.setProperty("http.proxyPort", proxyPort);
 816                 System.setProperty("https.proxyHost", proxyHost);
 817                 System.setProperty("https.proxyPort", proxyPort);
 818             } else if (proxyHost == null) {
 819                 throw new BadCommandLineException(
 820                     Messages.format(Messages.MISSING_PROXYHOST));
 821             } else {
 822                 throw new BadCommandLineException(
 823                     Messages.format(Messages.MISSING_PROXYPORT));
 824             }
 825             if(proxyUser!=null)
 826                 System.setProperty("http.proxyUser", proxyUser);
 827             if(proxyPassword!=null)
 828                 System.setProperty("http.proxyPassword", proxyPassword);
 829 
 830         }
 831 
 832         if (grammars.isEmpty())
 833             throw new BadCommandLineException(
 834                 Messages.format(Messages.MISSING_GRAMMAR));
 835 
 836         if( schemaLanguage==null )
 837             schemaLanguage = guessSchemaLanguage();
 838 
 839 //        if(target==SpecVersion.V2_2 && !isExtensionMode())
 840 //            throw new BadCommandLineException(
 841 //                "Currently 2.2 is still not finalized yet, so using it requires the -extension switch." +
 842 //                "NOTE THAT 2.2 SPEC MAY CHANGE BEFORE IT BECOMES FINAL.");
 843 
 844         if(pluginLoadFailure!=null)
 845             throw new BadCommandLineException(
 846                 Messages.format(Messages.PLUGIN_LOAD_FAILURE,pluginLoadFailure));
 847     }
 848 
 849     /**
 850      * Finds the <tt>META-INF/sun-jaxb.episode</tt> file to add as a binding customization.
 851      */
 852     public void scanEpisodeFile(File jar) throws BadCommandLineException {
 853         try {
 854             URLClassLoader ucl = new URLClassLoader(new URL[]{jar.toURL()});
 855             Enumeration<URL> resources = ucl.findResources("META-INF/sun-jaxb.episode");
 856             while (resources.hasMoreElements()) {
 857                 URL url = resources.nextElement();
 858                 addBindFile(new InputSource(url.toExternalForm()));
 859             }
 860         } catch (IOException e) {
 861             throw new BadCommandLineException(
 862                     Messages.format(Messages.FAILED_TO_LOAD,jar,e.getMessage()), e);
 863         }
 864     }
 865 
 866 
 867     /**
 868      * Guesses the schema language.
 869      */
 870     public Language guessSchemaLanguage() {
 871 
 872         // otherwise, use the file extension.
 873         // not a good solution, but very easy.
 874         if ((grammars != null) && (grammars.size() > 0)) {
 875             String name = grammars.get(0).getSystemId().toLowerCase();
 876 
 877             if (name.endsWith(".rng"))
 878                 return Language.RELAXNG;
 879             if (name.endsWith(".rnc"))
 880                 return Language.RELAXNG_COMPACT;
 881             if (name.endsWith(".dtd"))
 882                 return Language.DTD;
 883             if (name.endsWith(".wsdl"))
 884                 return Language.WSDL;
 885         }
 886 
 887         // by default, assume XML Schema
 888         return Language.XMLSCHEMA;
 889     }
 890 
 891     /**
 892      * Creates a configured CodeWriter that produces files into the specified directory.
 893      */
 894     public CodeWriter createCodeWriter() throws IOException {
 895         return createCodeWriter(new FileCodeWriter( targetDir, readOnly, encoding ));
 896     }
 897 
 898     /**
 899      * Creates a configured CodeWriter that produces files into the specified directory.
 900      */
 901     public CodeWriter createCodeWriter( CodeWriter core ) {
 902         if(noFileHeader)
 903             return core;
 904 
 905         return new PrologCodeWriter( core,getPrologComment() );
 906     }
 907 
 908     /**
 909      * Gets the string suitable to be used as the prolog comment baked into artifacts.
 910      * This is the string like "This file was generated by the JAXB RI on YYYY/mm/dd..."
 911      */
 912     public String getPrologComment() {
 913         // generate format syntax: <date> 'at' <time>
 914         String format =
 915             Messages.format(Messages.DATE_FORMAT)
 916                 + " '"
 917                 + Messages.format(Messages.AT)
 918                 + "' "
 919                 + Messages.format(Messages.TIME_FORMAT);
 920         SimpleDateFormat dateFormat = new SimpleDateFormat(format, Locale.ENGLISH);
 921 
 922         return Messages.format(
 923             Messages.FILE_PROLOG_COMMENT,
 924             dateFormat.format(new Date()));
 925     }
 926 
 927     /**
 928      * If a plugin failed to load, report.
 929      */
 930     private static String pluginLoadFailure;
 931 
 932     /**
 933      * Looks for all "META-INF/services/[className]" files and
 934      * create one instance for each class name found inside this file.
 935      */
 936     private static <T> T[] findServices( Class<T> clazz, ClassLoader classLoader ) {
 937         // if true, print debug output
 938         final boolean debug = com.sun.tools.internal.xjc.util.Util.getSystemProperty(Options.class,"findServices")!=null;
 939 
 940         // if we are running on Mustang or Dolphin, use ServiceLoader
 941         // so that we can take advantage of JSR-277 module system.
 942         try {
 943             Class<?> serviceLoader = Class.forName("java.util.ServiceLoader");
 944             if(debug)
 945                 System.out.println("Using java.util.ServiceLoader");
 946             Iterable<T> itr = (Iterable<T>)serviceLoader.getMethod("load",Class.class,ClassLoader.class).invoke(null,clazz,classLoader);
 947             List<T> r = new ArrayList<T>();
 948             for (T t : itr)
 949                 r.add(t);
 950             return r.toArray((T[])Array.newInstance(clazz,r.size()));
 951         } catch (ClassNotFoundException e) {
 952             // fall through
 953         } catch (IllegalAccessException e) {
 954             Error x = new IllegalAccessError();
 955             x.initCause(e);
 956             throw x;
 957         } catch (InvocationTargetException e) {
 958             Throwable x = e.getTargetException();
 959             if (x instanceof RuntimeException)
 960                 throw (RuntimeException) x;
 961             if (x instanceof Error)
 962                 throw (Error) x;
 963             throw new Error(x);
 964         } catch (NoSuchMethodException e) {
 965             Error x = new NoSuchMethodError();
 966             x.initCause(e);
 967             throw x;
 968         }
 969 
 970         String serviceId = "META-INF/services/" + clazz.getName();
 971 
 972         // used to avoid creating the same instance twice
 973         Set<String> classNames = new HashSet<String>();
 974 
 975         if(debug) {
 976             System.out.println("Looking for "+serviceId+" for add-ons");
 977         }
 978 
 979         // try to find services in CLASSPATH
 980         try {
 981             Enumeration<URL> e = classLoader.getResources(serviceId);
 982             if(e==null) return (T[])Array.newInstance(clazz,0);
 983 
 984             ArrayList<T> a = new ArrayList<T>();
 985             while(e.hasMoreElements()) {
 986                 URL url = e.nextElement();
 987                 BufferedReader reader=null;
 988 
 989                 if(debug) {
 990                     System.out.println("Checking "+url+" for an add-on");
 991                 }
 992 
 993                 try {
 994                     reader = new BufferedReader(new InputStreamReader(url.openStream()));
 995                     String impl;
 996                     while((impl = reader.readLine())!=null ) {
 997                         // try to instanciate the object
 998                         impl = impl.trim();
 999                         if(classNames.add(impl)) {
1000                             Class implClass = classLoader.loadClass(impl);
1001                             if(!clazz.isAssignableFrom(implClass)) {
1002                                 pluginLoadFailure = impl+" is not a subclass of "+clazz+". Skipping";
1003                                 if(debug)
1004                                     System.out.println(pluginLoadFailure);
1005                                 continue;
1006                             }
1007                             if(debug) {
1008                                 System.out.println("Attempting to instanciate "+impl);
1009                             }
1010                             a.add(clazz.cast(implClass.newInstance()));
1011                         }
1012                     }
1013                     reader.close();
1014                 } catch( Exception ex ) {
1015                     // let it go.
1016                     StringWriter w = new StringWriter();
1017                     ex.printStackTrace(new PrintWriter(w));
1018                     pluginLoadFailure = w.toString();
1019                     if(debug) {
1020                         System.out.println(pluginLoadFailure);
1021                     }
1022                     if( reader!=null ) {
1023                         try {
1024                             reader.close();
1025                         } catch( IOException ex2 ) {
1026                             // ignore
1027                         }
1028                     }
1029                 }
1030             }
1031 
1032             return a.toArray((T[])Array.newInstance(clazz,a.size()));
1033         } catch( Throwable e ) {
1034             // ignore any error
1035             StringWriter w = new StringWriter();
1036             e.printStackTrace(new PrintWriter(w));
1037             pluginLoadFailure = w.toString();
1038             if(debug) {
1039                 System.out.println(pluginLoadFailure);
1040             }
1041             return (T[])Array.newInstance(clazz,0);
1042         }
1043     }
1044 
1045     // this is a convenient place to expose the build version to xjc plugins
1046     public static String getBuildID() {
1047         return Messages.format(Messages.BUILD_ID);
1048     }
1049 }