|
1 /* |
|
2 * Copyright (c) 1997, 2011, 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.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 * Check the source schemas with extra scrutiny. |
|
110 * The exact meaning depends on the schema language. |
|
111 */ |
|
112 public boolean strictCheck =true; |
|
113 |
|
114 /** |
|
115 * If -explicit-annotation option is specified. |
|
116 * <p> |
|
117 * This generates code that works around issues specific to 1.4 runtime. |
|
118 */ |
|
119 public boolean runtime14 = false; |
|
120 |
|
121 /** |
|
122 * If true, try to resolve name conflicts automatically by assigning mechanical numbers. |
|
123 */ |
|
124 public boolean automaticNameConflictResolution = false; |
|
125 |
|
126 /** |
|
127 * strictly follow the compatibility rules and reject schemas that |
|
128 * contain features from App. E.2, use vendor binding extensions |
|
129 */ |
|
130 public static final int STRICT = 1; |
|
131 /** |
|
132 * loosely follow the compatibility rules and allow the use of vendor |
|
133 * binding extensions |
|
134 */ |
|
135 public static final int EXTENSION = 2; |
|
136 |
|
137 /** |
|
138 * this switch determines how carefully the compiler will follow |
|
139 * the compatibility rules in the spec. Either <code>STRICT</code> |
|
140 * or <code>EXTENSION</code>. |
|
141 */ |
|
142 public int compatibilityMode = STRICT; |
|
143 |
|
144 public boolean isExtensionMode() { |
|
145 return compatibilityMode==EXTENSION; |
|
146 } |
|
147 |
|
148 private static final Logger logger = com.sun.xml.internal.bind.Util.getClassLogger(); |
|
149 |
|
150 /** |
|
151 * Generates output for the specified version of the runtime. |
|
152 */ |
|
153 public SpecVersion target = SpecVersion.LATEST; |
|
154 |
|
155 private boolean is2_2 = true; |
|
156 |
|
157 public Options() { |
|
158 if (is2_2) { |
|
159 try { |
|
160 Class.forName("javax.xml.bind.JAXBPermission"); |
|
161 } catch (ClassNotFoundException cnfe) { |
|
162 is2_2 = false; |
|
163 } |
|
164 if (!is2_2) { |
|
165 target = SpecVersion.V2_1; |
|
166 } else { |
|
167 target = SpecVersion.LATEST; |
|
168 } |
|
169 } |
|
170 } |
|
171 |
|
172 /** |
|
173 * Target directory when producing files. |
|
174 * <p> |
|
175 * This field is not used when XJC is driven through the XJC API. |
|
176 * Plugins that need to generate extra files should do so by using |
|
177 * {@link JPackage#addResourceFile(JResourceFile)}. |
|
178 */ |
|
179 public File targetDir = new File("."); |
|
180 |
|
181 /** |
|
182 * Actually stores {@link CatalogResolver}, but the field |
|
183 * type is made to {@link EntityResolver} so that XJC can be |
|
184 * used even if resolver.jar is not available in the classpath. |
|
185 */ |
|
186 public EntityResolver entityResolver = null; |
|
187 |
|
188 /** |
|
189 * Type of input schema language. One of the <code>SCHEMA_XXX</code> |
|
190 * constants. |
|
191 */ |
|
192 private Language schemaLanguage = null; |
|
193 |
|
194 /** |
|
195 * The -p option that should control the default Java package that |
|
196 * will contain the generated code. Null if unspecified. |
|
197 */ |
|
198 public String defaultPackage = null; |
|
199 |
|
200 /** |
|
201 * Similar to the -p option, but this one works with a lower priority, |
|
202 * and customizations overrides this. Used by JAX-RPC. |
|
203 */ |
|
204 public String defaultPackage2 = null; |
|
205 |
|
206 /** |
|
207 * Input schema files as a list of {@link InputSource}s. |
|
208 */ |
|
209 private final List<InputSource> grammars = new ArrayList<InputSource>(); |
|
210 |
|
211 private final List<InputSource> bindFiles = new ArrayList<InputSource>(); |
|
212 |
|
213 // Proxy setting. |
|
214 private String proxyHost = null; |
|
215 private String proxyPort = null; |
|
216 private String proxyUser = null; |
|
217 private String proxyPassword = 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 URLClassLoader getUserClassLoader( ClassLoader parent ) { |
|
463 return new URLClassLoader( |
|
464 classpaths.toArray(new URL[classpaths.size()]),parent); |
|
465 } |
|
466 |
|
467 |
|
468 /** |
|
469 * Parses an option <code>args[i]</code> and return |
|
470 * the number of tokens consumed. |
|
471 * |
|
472 * @return |
|
473 * 0 if the argument is not understood. Returning 0 |
|
474 * will let the caller report an error. |
|
475 * @exception BadCommandLineException |
|
476 * If the callee wants to provide a custom message for an error. |
|
477 */ |
|
478 public int parseArgument( String[] args, int i ) throws BadCommandLineException { |
|
479 if (args[i].equals("-classpath") || args[i].equals("-cp")) { |
|
480 String a = requireArgument(args[i], args, ++i); |
|
481 for (String p : a.split(File.pathSeparator)) { |
|
482 File file = new File(p); |
|
483 try { |
|
484 classpaths.add(file.toURL()); |
|
485 } catch (MalformedURLException e) { |
|
486 throw new BadCommandLineException( |
|
487 Messages.format(Messages.NOT_A_VALID_FILENAME,file),e); |
|
488 } |
|
489 } |
|
490 return 2; |
|
491 } |
|
492 if (args[i].equals("-d")) { |
|
493 targetDir = new File(requireArgument("-d",args,++i)); |
|
494 if( !targetDir.exists() ) |
|
495 throw new BadCommandLineException( |
|
496 Messages.format(Messages.NON_EXISTENT_DIR,targetDir)); |
|
497 return 2; |
|
498 } |
|
499 if (args[i].equals("-readOnly")) { |
|
500 readOnly = true; |
|
501 return 1; |
|
502 } |
|
503 if (args[i].equals("-p")) { |
|
504 defaultPackage = requireArgument("-p",args,++i); |
|
505 if(defaultPackage.length()==0) { // user specified default package |
|
506 // there won't be any package to annotate, so disable them |
|
507 // automatically as a usability feature |
|
508 packageLevelAnnotations = false; |
|
509 } |
|
510 return 2; |
|
511 } |
|
512 if (args[i].equals("-debug")) { |
|
513 debugMode = true; |
|
514 verbose = true; |
|
515 return 1; |
|
516 } |
|
517 if (args[i].equals("-nv")) { |
|
518 strictCheck = false; |
|
519 return 1; |
|
520 } |
|
521 if( args[i].equals("-npa")) { |
|
522 packageLevelAnnotations = false; |
|
523 return 1; |
|
524 } |
|
525 if( args[i].equals("-no-header")) { |
|
526 noFileHeader = true; |
|
527 return 1; |
|
528 } |
|
529 if (args[i].equals("-verbose")) { |
|
530 verbose = true; |
|
531 return 1; |
|
532 } |
|
533 if (args[i].equals("-quiet")) { |
|
534 quiet = true; |
|
535 return 1; |
|
536 } |
|
537 if (args[i].equals("-XexplicitAnnotation")) { |
|
538 runtime14 = true; |
|
539 return 1; |
|
540 } |
|
541 if (args[i].equals("-enableIntrospection")) { |
|
542 enableIntrospection = true; |
|
543 return 1; |
|
544 } |
|
545 if (args[i].equals("-contentForWildcard")) { |
|
546 contentForWildcard = true; |
|
547 return 1; |
|
548 } |
|
549 if (args[i].equals("-XautoNameResolution")) { |
|
550 automaticNameConflictResolution = true; |
|
551 return 1; |
|
552 } |
|
553 if (args[i].equals("-b")) { |
|
554 addFile(requireArgument("-b",args,++i),bindFiles,".xjb"); |
|
555 return 2; |
|
556 } |
|
557 if (args[i].equals("-dtd")) { |
|
558 schemaLanguage = Language.DTD; |
|
559 return 1; |
|
560 } |
|
561 if (args[i].equals("-relaxng")) { |
|
562 schemaLanguage = Language.RELAXNG; |
|
563 return 1; |
|
564 } |
|
565 if (args[i].equals("-relaxng-compact")) { |
|
566 schemaLanguage = Language.RELAXNG_COMPACT; |
|
567 return 1; |
|
568 } |
|
569 if (args[i].equals("-xmlschema")) { |
|
570 schemaLanguage = Language.XMLSCHEMA; |
|
571 return 1; |
|
572 } |
|
573 if (args[i].equals("-wsdl")) { |
|
574 schemaLanguage = Language.WSDL; |
|
575 return 1; |
|
576 } |
|
577 if (args[i].equals("-extension")) { |
|
578 compatibilityMode = EXTENSION; |
|
579 return 1; |
|
580 } |
|
581 if (args[i].equals("-target")) { |
|
582 String token = requireArgument("-target",args,++i); |
|
583 target = SpecVersion.parse(token); |
|
584 if(target==null) |
|
585 throw new BadCommandLineException(Messages.format(Messages.ILLEGAL_TARGET_VERSION,token)); |
|
586 return 2; |
|
587 } |
|
588 if (args[i].equals("-httpproxyfile")) { |
|
589 if (i == args.length - 1 || args[i + 1].startsWith("-")) { |
|
590 throw new BadCommandLineException( |
|
591 Messages.format(Messages.MISSING_PROXYFILE)); |
|
592 } |
|
593 |
|
594 File file = new File(args[++i]); |
|
595 if(!file.exists()) { |
|
596 throw new BadCommandLineException( |
|
597 Messages.format(Messages.NO_SUCH_FILE,file)); |
|
598 } |
|
599 |
|
600 try { |
|
601 BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file),"UTF-8")); |
|
602 parseProxy(in.readLine()); |
|
603 in.close(); |
|
604 } catch (IOException e) { |
|
605 throw new BadCommandLineException( |
|
606 Messages.format(Messages.FAILED_TO_PARSE,file,e.getMessage()),e); |
|
607 } |
|
608 |
|
609 return 2; |
|
610 } |
|
611 if (args[i].equals("-httpproxy")) { |
|
612 if (i == args.length - 1 || args[i + 1].startsWith("-")) { |
|
613 throw new BadCommandLineException( |
|
614 Messages.format(Messages.MISSING_PROXY)); |
|
615 } |
|
616 |
|
617 parseProxy(args[++i]); |
|
618 return 2; |
|
619 } |
|
620 if (args[i].equals("-host")) { |
|
621 proxyHost = requireArgument("-host",args,++i); |
|
622 return 2; |
|
623 } |
|
624 if (args[i].equals("-port")) { |
|
625 proxyPort = requireArgument("-port",args,++i); |
|
626 return 2; |
|
627 } |
|
628 if( args[i].equals("-catalog") ) { |
|
629 // use Sun's "XML Entity and URI Resolvers" by Norman Walsh |
|
630 // to resolve external entities. |
|
631 // http://www.sun.com/xml/developers/resolver/ |
|
632 |
|
633 File catalogFile = new File(requireArgument("-catalog",args,++i)); |
|
634 try { |
|
635 addCatalog(catalogFile); |
|
636 } catch (IOException e) { |
|
637 throw new BadCommandLineException( |
|
638 Messages.format(Messages.FAILED_TO_PARSE,catalogFile,e.getMessage()),e); |
|
639 } |
|
640 return 2; |
|
641 } |
|
642 if (args[i].equals("-source")) { |
|
643 String version = requireArgument("-source",args,++i); |
|
644 //For source 1.0 the 1.0 Driver is loaded |
|
645 //Hence anything other than 2.0 is defaulted to |
|
646 //2.0 |
|
647 if( !version.equals("2.0") && !version.equals("2.1") ) |
|
648 throw new BadCommandLineException( |
|
649 Messages.format(Messages.DEFAULT_VERSION)); |
|
650 return 2; |
|
651 } |
|
652 if( args[i].equals("-Xtest-class-name-allocator") ) { |
|
653 classNameAllocator = new ClassNameAllocator() { |
|
654 public String assignClassName(String packageName, String className) { |
|
655 System.out.printf("assignClassName(%s,%s)\n",packageName,className); |
|
656 return className+"_Type"; |
|
657 } |
|
658 }; |
|
659 return 1; |
|
660 } |
|
661 |
|
662 if (args[i].equals("-encoding")) { |
|
663 encoding = requireArgument("-encoding", args, ++i); |
|
664 try { |
|
665 if (!Charset.isSupported(encoding)) { |
|
666 throw new BadCommandLineException( |
|
667 Messages.format(Messages.UNSUPPORTED_ENCODING, encoding)); |
|
668 } |
|
669 } catch (IllegalCharsetNameException icne) { |
|
670 throw new BadCommandLineException( |
|
671 Messages.format(Messages.UNSUPPORTED_ENCODING, encoding)); |
|
672 } |
|
673 return 2; |
|
674 } |
|
675 |
|
676 // see if this is one of the extensions |
|
677 for( Plugin plugin : getAllPlugins() ) { |
|
678 try { |
|
679 if( ('-'+plugin.getOptionName()).equals(args[i]) ) { |
|
680 activePlugins.add(plugin); |
|
681 plugin.onActivated(this); |
|
682 pluginURIs.addAll(plugin.getCustomizationURIs()); |
|
683 |
|
684 // give the plugin a chance to parse arguments to this option. |
|
685 // this is new in 2.1, and due to the backward compatibility reason, |
|
686 // if plugin didn't understand it, we still return 1 to indicate |
|
687 // that this option is consumed. |
|
688 int r = plugin.parseArgument(this,args,i); |
|
689 if(r!=0) |
|
690 return r; |
|
691 else |
|
692 return 1; |
|
693 } |
|
694 |
|
695 int r = plugin.parseArgument(this,args,i); |
|
696 if(r!=0) return r; |
|
697 } catch (IOException e) { |
|
698 throw new BadCommandLineException(e.getMessage(),e); |
|
699 } |
|
700 } |
|
701 |
|
702 return 0; // unrecognized |
|
703 } |
|
704 |
|
705 private void parseProxy(String text) throws BadCommandLineException { |
|
706 // syntax is [user[:password]@]proxyHost:proxyPort |
|
707 String token = "([^@:]+)"; |
|
708 Pattern p = Pattern.compile("(?:"+token+"(?:\\:"+token+")?\\@)?"+token+"(?:\\:"+token+")"); |
|
709 |
|
710 Matcher matcher = p.matcher(text); |
|
711 if(!matcher.matches()) |
|
712 throw new BadCommandLineException(Messages.format(Messages.ILLEGAL_PROXY,text)); |
|
713 |
|
714 proxyUser = matcher.group(1); |
|
715 proxyPassword = matcher.group(2); |
|
716 proxyHost = matcher.group(3); |
|
717 proxyPort = matcher.group(4); |
|
718 try { |
|
719 Integer.valueOf(proxyPort); |
|
720 } catch (NumberFormatException e) { |
|
721 throw new BadCommandLineException(Messages.format(Messages.ILLEGAL_PROXY,text)); |
|
722 } |
|
723 } |
|
724 |
|
725 /** |
|
726 * Obtains an operand and reports an error if it's not there. |
|
727 */ |
|
728 public String requireArgument(String optionName, String[] args, int i) throws BadCommandLineException { |
|
729 if (i == args.length || args[i].startsWith("-")) { |
|
730 throw new BadCommandLineException( |
|
731 Messages.format(Messages.MISSING_OPERAND,optionName)); |
|
732 } |
|
733 return args[i]; |
|
734 } |
|
735 |
|
736 /** |
|
737 * Parses a token to a file (or a set of files) |
|
738 * and add them as {@link InputSource} to the specified list. |
|
739 * |
|
740 * @param suffix |
|
741 * If the given token is a directory name, we do a recusive search |
|
742 * and find all files that have the given suffix. |
|
743 */ |
|
744 private void addFile(String name, List<InputSource> target, String suffix) throws BadCommandLineException { |
|
745 Object src; |
|
746 try { |
|
747 src = Util.getFileOrURL(name); |
|
748 } catch (IOException e) { |
|
749 throw new BadCommandLineException( |
|
750 Messages.format(Messages.NOT_A_FILE_NOR_URL,name)); |
|
751 } |
|
752 if(src instanceof URL) { |
|
753 target.add(absolutize(new InputSource(Util.escapeSpace(((URL)src).toExternalForm())))); |
|
754 } else { |
|
755 File fsrc = (File)src; |
|
756 if(fsrc.isDirectory()) { |
|
757 addRecursive(fsrc,suffix,target); |
|
758 } else { |
|
759 target.add(absolutize(fileToInputSource(fsrc))); |
|
760 } |
|
761 } |
|
762 } |
|
763 |
|
764 /** |
|
765 * Adds a new catalog file. |
|
766 */ |
|
767 public void addCatalog(File catalogFile) throws IOException { |
|
768 if(entityResolver==null) { |
|
769 CatalogManager.getStaticManager().setIgnoreMissingProperties(true); |
|
770 entityResolver = new CatalogResolver(true); |
|
771 } |
|
772 ((CatalogResolver)entityResolver).getCatalog().parseCatalog(catalogFile.getPath()); |
|
773 } |
|
774 |
|
775 /** |
|
776 * Parses arguments and fill fields of this object. |
|
777 * |
|
778 * @exception BadCommandLineException |
|
779 * thrown when there's a problem in the command-line arguments |
|
780 */ |
|
781 public void parseArguments( String[] args ) throws BadCommandLineException { |
|
782 |
|
783 for (int i = 0; i < args.length; i++) { |
|
784 if(args[i].length()==0) |
|
785 throw new BadCommandLineException(); |
|
786 if (args[i].charAt(0) == '-') { |
|
787 int j = parseArgument(args,i); |
|
788 if(j==0) |
|
789 throw new BadCommandLineException( |
|
790 Messages.format(Messages.UNRECOGNIZED_PARAMETER, args[i])); |
|
791 i += (j-1); |
|
792 } else { |
|
793 if(args[i].endsWith(".jar")) |
|
794 scanEpisodeFile(new File(args[i])); |
|
795 else |
|
796 addFile(args[i],grammars,".xsd"); |
|
797 } |
|
798 } |
|
799 |
|
800 // configure proxy |
|
801 if (proxyHost != null || proxyPort != null) { |
|
802 if (proxyHost != null && proxyPort != null) { |
|
803 System.setProperty("http.proxyHost", proxyHost); |
|
804 System.setProperty("http.proxyPort", proxyPort); |
|
805 System.setProperty("https.proxyHost", proxyHost); |
|
806 System.setProperty("https.proxyPort", proxyPort); |
|
807 } else if (proxyHost == null) { |
|
808 throw new BadCommandLineException( |
|
809 Messages.format(Messages.MISSING_PROXYHOST)); |
|
810 } else { |
|
811 throw new BadCommandLineException( |
|
812 Messages.format(Messages.MISSING_PROXYPORT)); |
|
813 } |
|
814 if(proxyUser!=null) |
|
815 System.setProperty("http.proxyUser", proxyUser); |
|
816 if(proxyPassword!=null) |
|
817 System.setProperty("http.proxyPassword", proxyPassword); |
|
818 |
|
819 } |
|
820 |
|
821 if (grammars.isEmpty()) |
|
822 throw new BadCommandLineException( |
|
823 Messages.format(Messages.MISSING_GRAMMAR)); |
|
824 |
|
825 if( schemaLanguage==null ) |
|
826 schemaLanguage = guessSchemaLanguage(); |
|
827 |
|
828 // if(target==SpecVersion.V2_2 && !isExtensionMode()) |
|
829 // throw new BadCommandLineException( |
|
830 // "Currently 2.2 is still not finalized yet, so using it requires the -extension switch." + |
|
831 // "NOTE THAT 2.2 SPEC MAY CHANGE BEFORE IT BECOMES FINAL."); |
|
832 |
|
833 if(pluginLoadFailure!=null) |
|
834 throw new BadCommandLineException( |
|
835 Messages.format(Messages.PLUGIN_LOAD_FAILURE,pluginLoadFailure)); |
|
836 } |
|
837 |
|
838 /** |
|
839 * Finds the <tt>META-INF/sun-jaxb.episode</tt> file to add as a binding customization. |
|
840 */ |
|
841 public void scanEpisodeFile(File jar) throws BadCommandLineException { |
|
842 try { |
|
843 URLClassLoader ucl = new URLClassLoader(new URL[]{jar.toURL()}); |
|
844 Enumeration<URL> resources = ucl.findResources("META-INF/sun-jaxb.episode"); |
|
845 while (resources.hasMoreElements()) { |
|
846 URL url = resources.nextElement(); |
|
847 addBindFile(new InputSource(url.toExternalForm())); |
|
848 } |
|
849 } catch (IOException e) { |
|
850 throw new BadCommandLineException( |
|
851 Messages.format(Messages.FAILED_TO_LOAD,jar,e.getMessage()), e); |
|
852 } |
|
853 } |
|
854 |
|
855 |
|
856 /** |
|
857 * Guesses the schema language. |
|
858 */ |
|
859 public Language guessSchemaLanguage() { |
|
860 |
|
861 // otherwise, use the file extension. |
|
862 // not a good solution, but very easy. |
|
863 if ((grammars != null) && (grammars.size() > 0)) { |
|
864 String name = grammars.get(0).getSystemId().toLowerCase(); |
|
865 |
|
866 if (name.endsWith(".rng")) |
|
867 return Language.RELAXNG; |
|
868 if (name.endsWith(".rnc")) |
|
869 return Language.RELAXNG_COMPACT; |
|
870 if (name.endsWith(".dtd")) |
|
871 return Language.DTD; |
|
872 if (name.endsWith(".wsdl")) |
|
873 return Language.WSDL; |
|
874 } |
|
875 |
|
876 // by default, assume XML Schema |
|
877 return Language.XMLSCHEMA; |
|
878 } |
|
879 |
|
880 /** |
|
881 * Creates a configured CodeWriter that produces files into the specified directory. |
|
882 */ |
|
883 public CodeWriter createCodeWriter() throws IOException { |
|
884 return createCodeWriter(new FileCodeWriter( targetDir, readOnly, encoding )); |
|
885 } |
|
886 |
|
887 /** |
|
888 * Creates a configured CodeWriter that produces files into the specified directory. |
|
889 */ |
|
890 public CodeWriter createCodeWriter( CodeWriter core ) { |
|
891 if(noFileHeader) |
|
892 return core; |
|
893 |
|
894 return new PrologCodeWriter( core,getPrologComment() ); |
|
895 } |
|
896 |
|
897 /** |
|
898 * Gets the string suitable to be used as the prolog comment baked into artifacts. |
|
899 * This is the string like "This file was generated by the JAXB RI on YYYY/mm/dd..." |
|
900 */ |
|
901 public String getPrologComment() { |
|
902 // generate format syntax: <date> 'at' <time> |
|
903 String format = |
|
904 Messages.format(Messages.DATE_FORMAT) |
|
905 + " '" |
|
906 + Messages.format(Messages.AT) |
|
907 + "' " |
|
908 + Messages.format(Messages.TIME_FORMAT); |
|
909 SimpleDateFormat dateFormat = new SimpleDateFormat(format, Locale.ENGLISH); |
|
910 |
|
911 return Messages.format( |
|
912 Messages.FILE_PROLOG_COMMENT, |
|
913 dateFormat.format(new Date())); |
|
914 } |
|
915 |
|
916 /** |
|
917 * If a plugin failed to load, report. |
|
918 */ |
|
919 private static String pluginLoadFailure; |
|
920 |
|
921 /** |
|
922 * Looks for all "META-INF/services/[className]" files and |
|
923 * create one instance for each class name found inside this file. |
|
924 */ |
|
925 private static <T> T[] findServices( Class<T> clazz, ClassLoader classLoader ) { |
|
926 // if true, print debug output |
|
927 final boolean debug = com.sun.tools.internal.xjc.util.Util.getSystemProperty(Options.class,"findServices")!=null; |
|
928 |
|
929 // if we are running on Mustang or Dolphin, use ServiceLoader |
|
930 // so that we can take advantage of JSR-277 module system. |
|
931 try { |
|
932 Class<?> serviceLoader = Class.forName("java.util.ServiceLoader"); |
|
933 if(debug) |
|
934 System.out.println("Using java.util.ServiceLoader"); |
|
935 Iterable<T> itr = (Iterable<T>)serviceLoader.getMethod("load",Class.class,ClassLoader.class).invoke(null,clazz,classLoader); |
|
936 List<T> r = new ArrayList<T>(); |
|
937 for (T t : itr) |
|
938 r.add(t); |
|
939 return r.toArray((T[])Array.newInstance(clazz,r.size())); |
|
940 } catch (ClassNotFoundException e) { |
|
941 // fall through |
|
942 } catch (IllegalAccessException e) { |
|
943 Error x = new IllegalAccessError(); |
|
944 x.initCause(e); |
|
945 throw x; |
|
946 } catch (InvocationTargetException e) { |
|
947 Throwable x = e.getTargetException(); |
|
948 if (x instanceof RuntimeException) |
|
949 throw (RuntimeException) x; |
|
950 if (x instanceof Error) |
|
951 throw (Error) x; |
|
952 throw new Error(x); |
|
953 } catch (NoSuchMethodException e) { |
|
954 Error x = new NoSuchMethodError(); |
|
955 x.initCause(e); |
|
956 throw x; |
|
957 } |
|
958 |
|
959 String serviceId = "META-INF/services/" + clazz.getName(); |
|
960 |
|
961 // used to avoid creating the same instance twice |
|
962 Set<String> classNames = new HashSet<String>(); |
|
963 |
|
964 if(debug) { |
|
965 System.out.println("Looking for "+serviceId+" for add-ons"); |
|
966 } |
|
967 |
|
968 // try to find services in CLASSPATH |
|
969 try { |
|
970 Enumeration<URL> e = classLoader.getResources(serviceId); |
|
971 if(e==null) return (T[])Array.newInstance(clazz,0); |
|
972 |
|
973 ArrayList<T> a = new ArrayList<T>(); |
|
974 while(e.hasMoreElements()) { |
|
975 URL url = e.nextElement(); |
|
976 BufferedReader reader=null; |
|
977 |
|
978 if(debug) { |
|
979 System.out.println("Checking "+url+" for an add-on"); |
|
980 } |
|
981 |
|
982 try { |
|
983 reader = new BufferedReader(new InputStreamReader(url.openStream())); |
|
984 String impl; |
|
985 while((impl = reader.readLine())!=null ) { |
|
986 // try to instanciate the object |
|
987 impl = impl.trim(); |
|
988 if(classNames.add(impl)) { |
|
989 Class implClass = classLoader.loadClass(impl); |
|
990 if(!clazz.isAssignableFrom(implClass)) { |
|
991 pluginLoadFailure = impl+" is not a subclass of "+clazz+". Skipping"; |
|
992 if(debug) |
|
993 System.out.println(pluginLoadFailure); |
|
994 continue; |
|
995 } |
|
996 if(debug) { |
|
997 System.out.println("Attempting to instanciate "+impl); |
|
998 } |
|
999 a.add(clazz.cast(implClass.newInstance())); |
|
1000 } |
|
1001 } |
|
1002 reader.close(); |
|
1003 } catch( Exception ex ) { |
|
1004 // let it go. |
|
1005 StringWriter w = new StringWriter(); |
|
1006 ex.printStackTrace(new PrintWriter(w)); |
|
1007 pluginLoadFailure = w.toString(); |
|
1008 if(debug) { |
|
1009 System.out.println(pluginLoadFailure); |
|
1010 } |
|
1011 if( reader!=null ) { |
|
1012 try { |
|
1013 reader.close(); |
|
1014 } catch( IOException ex2 ) { |
|
1015 // ignore |
|
1016 } |
|
1017 } |
|
1018 } |
|
1019 } |
|
1020 |
|
1021 return a.toArray((T[])Array.newInstance(clazz,a.size())); |
|
1022 } catch( Throwable e ) { |
|
1023 // ignore any error |
|
1024 StringWriter w = new StringWriter(); |
|
1025 e.printStackTrace(new PrintWriter(w)); |
|
1026 pluginLoadFailure = w.toString(); |
|
1027 if(debug) { |
|
1028 System.out.println(pluginLoadFailure); |
|
1029 } |
|
1030 return (T[])Array.newInstance(clazz,0); |
|
1031 } |
|
1032 } |
|
1033 |
|
1034 // this is a convenient place to expose the build version to xjc plugins |
|
1035 public static String getBuildID() { |
|
1036 return Messages.format(Messages.BUILD_ID); |
|
1037 } |
|
1038 |
|
1039 public static String normalizeSystemId(String systemId) { |
|
1040 try { |
|
1041 systemId = new URI(systemId).normalize().toString(); |
|
1042 } catch (URISyntaxException e) { |
|
1043 // leave the system ID untouched. In my experience URI is often too strict |
|
1044 } |
|
1045 return systemId; |
|
1046 } |
|
1047 |
|
1048 } |