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