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