View Javadoc
1   package org.codehaus.mojo.jaxb2.javageneration;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import com.sun.tools.xjc.Driver;
23  import org.apache.maven.model.Resource;
24  import org.apache.maven.plugin.MojoExecutionException;
25  import org.apache.maven.plugin.MojoFailureException;
26  import org.apache.maven.plugins.annotations.Parameter;
27  import org.apache.maven.settings.Proxy;
28  import org.apache.maven.settings.Settings;
29  import org.codehaus.mojo.jaxb2.AbstractJaxbMojo;
30  import org.codehaus.mojo.jaxb2.NoSchemasException;
31  import org.codehaus.mojo.jaxb2.shared.FileSystemUtilities;
32  import org.codehaus.mojo.jaxb2.shared.arguments.ArgumentBuilder;
33  import org.codehaus.mojo.jaxb2.shared.environment.EnvironmentFacet;
34  import org.codehaus.mojo.jaxb2.shared.environment.ToolExecutionEnvironment;
35  import org.codehaus.mojo.jaxb2.shared.environment.classloading.ThreadContextClassLoaderBuilder;
36  import org.codehaus.mojo.jaxb2.shared.environment.locale.LocaleFacet;
37  import org.codehaus.mojo.jaxb2.shared.environment.logging.LoggingHandlerEnvironmentFacet;
38  import org.codehaus.plexus.util.FileUtils;
39  import org.codehaus.plexus.util.IOUtil;
40  
41  import java.io.File;
42  import java.io.FileWriter;
43  import java.net.HttpURLConnection;
44  import java.net.URL;
45  import java.net.URLConnection;
46  import java.util.ArrayList;
47  import java.util.Arrays;
48  import java.util.List;
49  
50  /**
51   * <p>Abstract superclass for Mojos generating Java source or binaries from XML schema(s) by invoking the JAXB XJC
52   * binding compiler. Most of the Configuration options for the AbstractJavaGeneratorMojo are set or copied to the
53   * XJC directly; refer to their documentation in the <a href="https://jaxb.java.net/">JAXB Reference Implementation</a>
54   * site.</p>
55   *
56   * @author <a href="mailto:lj@jguru.se">Lennart J&ouml;relid</a>
57   * @see <a href="https://jaxb.java.net/">The JAXB Reference Implementation</a>
58   */
59  public abstract class AbstractJavaGeneratorMojo extends AbstractJaxbMojo {
60  
61      private static final int XJC_COMPLETED_OK = 0;
62  
63      /**
64       * <p>Corresponding XJC parameter: {@code catalog}.</p>
65       * <p>Specify catalog files to resolve external entity references.
66       * Supports TR9401, XCatalog, and OASIS XML Catalog format.</p>
67       */
68      @Parameter
69      protected File catalog;
70  
71      /**
72       * <p>Corresponding XJC parameter: {@code episode}.</p>
73       * <p>Generate an episode file from this compilation, so that other schemas that rely on this schema can be
74       * compiled later and rely on classes that are generated from this compilation. The generated episode file is
75       * really just a JAXB customization file (but with vendor extensions.)</p>
76       * <p>If this parameter is {@code true}, the episode file generated is called {@code META-INF/sun-jaxb.episode},
77       * and included in the artifact.</p>
78       *
79       * @see #STANDARD_EPISODE_FILENAME
80       * @since 2.0
81       */
82      @Parameter(defaultValue = "true")
83      protected boolean generateEpisode;
84  
85      /**
86       * <p>Sets the HTTP/HTTPS proxy to be used by the XJC, on the format
87       * {@code [user[:password]@]proxyHost[:proxyPort]}.
88       * All information is retrieved from the active proxy within the standard maven settings file.</p>
89       */
90      @Parameter(defaultValue = "${settings}", readonly = true)
91      protected Settings settings;
92  
93      /**
94       * <p>Defines the content type of sources for the XJC. To simplify usage of the JAXB2 maven plugin,
95       * all source files are assumed to have the same type of content.</p>
96       * <p>This parameter replaces the previous multiple-choice boolean configuration options for the
97       * jaxb2-maven-plugin (i.e. dtd, xmlschema, relaxng, relaxng-compact, wsdl), and
98       * corresponds to setting one of those flags as an XJC argument.</p>
99       *
100      * @since 2.0
101      */
102     @Parameter(defaultValue = "XmlSchema")
103     protected SourceContentType sourceType;
104 
105     /**
106      * <p>Corresponding XJC parameter: {@code npa}.</p>
107      * <p>Suppress the generation of package level annotations into {@code package-info.java}.
108      * Using this switch causes the generated code to internalize those annotations into the other
109      * generated classes.</p>
110      *
111      * @since 2.0
112      */
113     @Parameter(defaultValue = "false")
114     protected boolean noPackageLevelAnnotations;
115 
116     /**
117      * <p>Corresponding XJC parameter: {@code no-header}.</p>
118      * <p>Suppress the generation of a file header comment that includes some note and timestamp.
119      * Using this makes the generated code more diff-friendly.</p>
120      *
121      * @since 2.0
122      */
123     @Parameter(defaultValue = "false")
124     protected boolean noGeneratedHeaderComments;
125 
126     /**
127      * <p>Corresponding XJC parameter: {@code mark-generated}.</p>
128      * <p>This feature causes all of the generated code to have {@code @Generated} annotation.</p>
129      *
130      * @since 2.0
131      */
132     @Parameter(defaultValue = "false")
133     protected boolean addGeneratedAnnotation;
134 
135     /**
136      * <p>Corresponding XJC parameter: {@code nv}.</p>
137      * <p>By default, the XJC binding compiler performs strict validation of the source schema before processing it.
138      * Use this option to disable strict schema validation. This does not mean that the binding compiler will not
139      * perform any validation, it simply means that it will perform less-strict validation.</p>
140      *
141      * @since 2.0
142      */
143     @Parameter(defaultValue = "false")
144     protected boolean laxSchemaValidation;
145 
146     /**
147      * <p>Corresponding XJC parameter: {@code quiet}.</p>
148      * <p>Suppress compiler output, such as progress information and warnings.</p>
149      */
150     @Parameter(defaultValue = "false")
151     protected boolean quiet;
152 
153     /**
154      * <p>Corresponding XJC parameter: {@code verbose}.</p>
155      * <p>Tells XJC to be extra verbose, such as printing informational messages or displaying stack traces.</p>
156      */
157     @Parameter(property = "xjc.verbose", defaultValue = "false")
158     protected boolean verbose;
159 
160     /**
161      * <p>Corresponding XJC parameter: {@code extension}.</p>
162      * <p>By default, the XJC binding compiler strictly enforces the rules outlined in the Compatibility chapter of
163      * the JAXB Specification. Appendix E.2 defines a set of W3C XML Schema features that are not completely
164      * supported by JAXB v1.0. In some cases, you may be allowed to use them in the "-extension" mode enabled by
165      * this switch. In the default (strict) mode, you are also limited to using only the binding customizations
166      * defined in the specification.</p>
167      */
168     @Parameter(defaultValue = "false")
169     protected boolean extension;
170 
171     /**
172      * Fails the Mojo execution if no XSDs/schemas are found.
173      *
174      * @since 1.3
175      */
176     @Parameter(defaultValue = "true")
177     protected boolean failOnNoSchemas;
178 
179     /**
180      * <p>Removes all files from the output directory before running XJC.</p>
181      */
182     @Parameter(defaultValue = "true")
183     protected boolean clearOutputDir;
184 
185     /**
186      * <p>Corresponding XJC parameter: {@code readOnly}.</p>
187      * <p>By default, the XJC binding compiler does not write-protect the Java source files it generates.
188      * Use this option to force the XJC binding compiler to mark the generated Java sources read-only.</p>
189      *
190      * @since 2.0
191      */
192     @Parameter(defaultValue = "false")
193     protected boolean readOnly;
194 
195     /**
196      * <p>List of ordered extra arguments to the XJC command. Each extra argument is interpreted as a word, intended
197      * to be copied verbatim to the XJC argument list with spaces in between:</p>
198      * <pre>
199      * <code>
200      *   &lt;configuration&gt;
201      *   ...
202      *       &lt;arguments&gt;
203      *          &lt;argument&gt;-Xfluent-api&lt;/argument&gt;
204      *          &lt;argument&gt;somefile&lt;/argument&gt;
205      *      &lt;/arguments&gt;
206      *   &lt;/configuration&gt;
207      * </code>
208      * </pre>
209      * <p>The arguments configured above yields the following extra arguments to the XJC command:
210      * <code>-Xfluent-api -episode somefile</code></p>
211      *
212      * @since 2.0
213      * @deprecated This should be removed in the 2.0+ release, as all arguments should be handled by other parameters.
214      */
215     @Parameter(property = "xjc.arguments")
216     protected List<String> arguments;
217 
218     /**
219      * <p>Corresponding XJC parameter: {@code enableIntrospection}.</p>
220      * <p>Enable correct generation of Boolean getters/setters to enable Bean Introspection APIs.</p>
221      *
222      * @since 1.4
223      */
224     @Parameter(defaultValue = "false")
225     private boolean enableIntrospection;
226 
227     /**
228      * <p>Corresponding XJC parameter: {@code p}.</p>
229      * <p>The package under which the source files will be generated. Quoting the XJC documentation:
230      * "Specifying a target package via this command-line option overrides any binding customization for package
231      * name and the default package name algorithm defined in the specification".</p>
232      */
233     @Parameter
234     protected String packageName;
235 
236     /**
237      * <p>Corresponding XJC parameter: {@code target}.</p>
238      * <p>Permitted values: {@code "2.0"} and {@code "2.1"}. Avoid generating code that relies on JAXB newer than the
239      * version given. This will allow the generated code to run with JAXB 2.0 runtime (such as JavaSE 6.)</p>
240      *
241      * @since 1.3
242      */
243     @Parameter
244     protected String target;
245 
246     /**
247      * <p>If provided, this parameter indicates that the XSDs used by XJC to generate Java code should be
248      * copied into the resulting artifact of this project (the JAR, WAR or whichever artifact type is generated).
249      * The value of the {@code xsdPathWithinArtifact} parameter is the relative path within the artifact where
250      * all source XSDs are copied to (hence the name "XSD Path Within Artifact").</p>
251      * <p>The target directory is created within the artifact if it does not already exist.
252      * If the {@code xsdPathWithinArtifact} parameter is not given, the XSDs used to generate Java code are
253      * <em>not</em> included within the project's artifact.</p>
254      * <p><em>Example:</em>Adding the sample configuration below would copy all source XSDs to the given directory
255      * within the resulting JAR (and/or test-JAR). If the directory {@code META-INF/jaxb/xsd} does not exist, it
256      * will be created.</p>
257      * <pre>
258      *     <code>
259      *         &lt;configuration&gt;
260      *             ...
261      *             &lt;xsdPathWithinArtifact&gt;META-INF/jaxb/xsd&lt;/xsdPathWithinArtifact&gt;
262      *         &lt;/configuration&gt;
263      *     </code>
264      * </pre>
265      * <p><strong>Note</strong>: This parameter was previously called {@code includeSchemasOutputPath}
266      * in the 1.x versions of this plugin, but was renamed and re-documented for improved usability and clarity.</p>
267      *
268      * @since 2.0
269      */
270     @Parameter
271     protected String xsdPathWithinArtifact;
272 
273     /**
274      * <p>Java generation is required if any of the file products is outdated/stale.</p>
275      * {@inheritDoc}
276      */
277     @Override
278     protected boolean isReGenerationRequired() {
279 
280         //
281         // Use the stale flag method to identify if we should re-generate the java source code from the supplied
282         // Xml Schema. Basically, we should regenerate the JAXB code if:
283         //
284         // a) The staleFile does not exist
285         // b) The staleFile exists and is older than one of the sources (XSD or XJB files).
286         //    "Older" is determined by comparing the modification timestamp of the staleFile and the source files.
287         //
288         final File staleFile = getStaleFile();
289         final String debugPrefix = "StaleFile [" + FileSystemUtilities.getCanonicalPath(staleFile) + "]";
290 
291         boolean stale = !staleFile.exists();
292         if (stale) {
293             getLog().debug(debugPrefix + " not found. JAXB (re-)generation required.");
294         } else {
295 
296             final List<URL> sourceXSDs = getSources();
297             final List<File> sourceXJBs = getSourceXJBs();
298 
299             if (getLog().isDebugEnabled()) {
300                 getLog().debug(debugPrefix + " found. Checking timestamps on source XSD and XJB "
301                         + "files to determine if JAXB (re-)generation is required.");
302             }
303 
304             final long staleFileLastModified = staleFile.lastModified();
305             for (URL current : sourceXSDs) {
306 
307                 final URLConnection sourceXsdConnection;
308                 try {
309                     sourceXsdConnection = current.openConnection();
310                     sourceXsdConnection.connect();
311                 } catch (Exception e) {
312 
313                     // Can't determine if the staleFile is younger than this sourceXSD.
314                     // Re-generate to be on the safe side.
315                     stale = true;
316                     break;
317                 }
318 
319                 try {
320                     if (sourceXsdConnection.getLastModified() > staleFileLastModified) {
321 
322                         if (getLog().isDebugEnabled()) {
323                             getLog().debug(current.toString() + " is newer than the stale flag file.");
324                         }
325                         stale = true;
326                     }
327                 } finally {
328                     if (sourceXsdConnection instanceof HttpURLConnection) {
329                         ((HttpURLConnection) sourceXsdConnection).disconnect();
330                     }
331                 }
332             }
333 
334             for (File current : sourceXJBs) {
335                 if (current.lastModified() > staleFileLastModified) {
336 
337                     if (getLog().isDebugEnabled()) {
338                         getLog().debug(FileSystemUtilities.getCanonicalPath(current)
339                                 + " is newer than the stale flag file.");
340                     }
341 
342                     stale = true;
343                     break;
344                 }
345             }
346         }
347 
348         // All done.
349         return stale;
350     }
351 
352     /**
353      * {@inheritDoc}
354      */
355     @Override
356     protected boolean performExecution() throws MojoExecutionException, MojoFailureException {
357 
358         boolean updateStaleFileTimestamp = false;
359 
360         try {
361 
362             // Setup the Tool's execution environment
363             ToolExecutionEnvironment environment = null;
364             try {
365 
366                 // Create a LocaleFacet if the user has configured an explicit Locale for the tool.
367                 final LocaleFacet localeFacet = locale == null ? null : LocaleFacet.createFor(locale, getLog());
368 
369                 // Create the ToolExecutionEnvironment
370                 environment = new ToolExecutionEnvironment(getLog(),
371                         ThreadContextClassLoaderBuilder.createFor(this.getClass(), getLog(), getEncoding(false))
372                                 .addPaths(getClasspath()),
373                         LoggingHandlerEnvironmentFacet.create(getLog(), getClass(), getEncoding(false)),
374                         localeFacet);
375 
376                 // Add any extra configured EnvironmentFacets, as configured in the POM.
377                 if (extraFacets != null) {
378                     for (EnvironmentFacet current : extraFacets) {
379                         environment.add(current);
380                     }
381                 }
382 
383                 // Setup the environment.
384                 environment.setup();
385 
386                 // Compile the XJC arguments
387                 final String[] xjcArguments = getXjcArguments(
388                         environment.getClassPathAsArgument(),
389                         STANDARD_EPISODE_FILENAME);
390 
391                 // Ensure that the outputDirectory exists, but only clear it if does not already
392                 FileSystemUtilities.createDirectory(getOutputDirectory(), clearOutputDir);
393 
394                 // Do we need to re-create the episode file's parent directory.
395                 final boolean reCreateEpisodeFileParentDirectory = generateEpisode && clearOutputDir;
396                 if (reCreateEpisodeFileParentDirectory) {
397                     getEpisodeFile(STANDARD_EPISODE_FILENAME);
398                 }
399 
400                 // Check the system properties.
401                 logSystemPropertiesAndBasedir();
402 
403                 // Fire XJC
404                 if (XJC_COMPLETED_OK != Driver.run(xjcArguments, new XjcLogAdapter(getLog()))) {
405 
406                     final StringBuilder errorMsgBuilder = new StringBuilder();
407                     errorMsgBuilder.append("\n+=================== [XJC Error]\n");
408                     errorMsgBuilder.append("|\n");
409 
410                     final List<URL> sourceXSDs = getSources();
411                     for (int i = 0; i < sourceXSDs.size(); i++) {
412                         errorMsgBuilder.append("| " + i + ": ").append(sourceXSDs.get(i).toString()).append("\n");
413                     }
414 
415                     errorMsgBuilder.append("|\n");
416                     errorMsgBuilder.append("+=================== [End XJC Error]\n");
417                     throw new MojoExecutionException(errorMsgBuilder.toString());
418                 }
419 
420                 // Indicate that the output directory was updated.
421                 getBuildContext().refresh(getOutputDirectory());
422 
423                 // Update the modification timestamp of the staleFile.
424                 updateStaleFileTimestamp = true;
425 
426             } finally {
427 
428                 if (environment != null) {
429                     environment.restore();
430                 }
431             }
432 
433             // Add the generated source root to the project, enabling tooling and other plugins to see them.
434             addGeneratedSourcesToProjectSourceRoot();
435 
436             // Copy all source XSDs to the resulting artifact?
437             if (xsdPathWithinArtifact != null) {
438 
439                 final String buildOutputDirectory = getProject().getBuild().getOutputDirectory();
440                 final File targetXsdDirectory = new File(buildOutputDirectory, xsdPathWithinArtifact);
441                 FileUtils.forceMkdir(targetXsdDirectory);
442 
443                 for (URL current : getSources()) {
444 
445                     String fileName = null;
446                     if ("file".equalsIgnoreCase(current.getProtocol())) {
447                         fileName = new File(current.getPath()).getName();
448                     } else if ("jar".equalsIgnoreCase(current.getProtocol())) {
449 
450                         // Typical JAR path
451                         // jar:file:/path/to/aJar.jar!/some/path/xsd/aResource.xsd
452                         final int bangIndex = current.toString().indexOf("!");
453                         if (bangIndex == -1) {
454                             throw new MojoExecutionException("Illegal JAR URL [" + current.toString()
455                                     + "]: lacks a '!'");
456                         }
457 
458                         final String internalPath = current.toString().substring(bangIndex + 1);
459                         fileName = new File(internalPath).getName();
460                     } else {
461                         throw new MojoExecutionException("Could not extract FileName from URL [" + current + "]");
462                     }
463 
464                     final File targetFile = new File(targetXsdDirectory, fileName);
465                     if (targetFile.exists()) {
466 
467                         // TODO: Should we throw an exception here instead?
468                         getLog().warn("File [" + FileSystemUtilities.getCanonicalPath(targetFile)
469                                 + "] already exists. Not copying XSD file [" + current.getPath() + "] to it.");
470                     }
471                     IOUtil.copy(current.openStream(), new FileWriter(targetFile));
472                 }
473 
474                 // Refresh the BuildContext
475                 getBuildContext().refresh(targetXsdDirectory);
476             }
477         } catch (MojoExecutionException e) {
478             throw e;
479         } catch (NoSchemasException e) {
480             if (failOnNoSchemas) {
481                 throw new MojoExecutionException("", e);
482             }
483         } catch (Exception e) {
484             throw new MojoExecutionException(e.getMessage(), e);
485         }
486 
487         // All done.
488         return updateStaleFileTimestamp;
489     }
490 
491     /**
492      * Override this method to acquire a List holding all URLs to the JAXB sources for which this
493      * AbstractJavaGeneratorMojo should generate Java files. Sources are assumed to be in the form given by
494      * the {@code sourceType} value.
495      *
496      * @return A non-null List holding URLs to sources for the XJC generation.
497      * @see #sourceType
498      */
499     @Override
500     protected abstract List<URL> getSources();
501 
502     /**
503      * Override this method to retrieve a list of Files to all XJB files for which this
504      * AbstractJavaGeneratorMojo should generate Java files.
505      *
506      * @return A non-null List holding binding files.
507      */
508     protected abstract List<File> getSourceXJBs();
509 
510     /**
511      * Adds any directories containing the generated XJC classes to the appropriate Project compilation sources;
512      * either {@code TestCompileSourceRoot} or {@code CompileSourceRoot} depending on the exact Mojo implementation
513      * of this AbstractJavaGeneratorMojo.
514      */
515     protected abstract void addGeneratedSourcesToProjectSourceRoot();
516 
517     /**
518      * Adds the supplied Resource to the project using the appropriate scope (i.e. resource or testResource)
519      * depending on the exact implementation of this AbstractJavaGeneratorMojo.
520      *
521      * @param resource The resource to add.
522      */
523     protected abstract void addResource(final Resource resource);
524 
525     //
526     // Private helpers
527     //
528 
529     private String[] getXjcArguments(final String classPath, final String episodeFileNameOrNull)
530             throws MojoExecutionException, NoSchemasException {
531 
532         final ArgumentBuilder builder = new ArgumentBuilder();
533 
534         // Add all flags on the form '-flagName'
535         builder.withFlag(true, sourceType.getXjcArgument());
536         builder.withFlag(noPackageLevelAnnotations, "npa");
537         builder.withFlag(laxSchemaValidation, "nv");
538         builder.withFlag(verbose, "verbose");
539         builder.withFlag(quiet, "quiet");
540         builder.withFlag(enableIntrospection, "enableIntrospection");
541         builder.withFlag(extension, "extension");
542         builder.withFlag(readOnly, "readOnly");
543         builder.withFlag(noGeneratedHeaderComments, "no-header");
544         builder.withFlag(addGeneratedAnnotation, "mark-generated");
545 
546         // Add all arguments on the form '-argumentName argumentValue'
547         // (i.e. in 2 separate elements of the returned String[])
548         builder.withNamedArgument("httpproxy", getProxyString(settings.getActiveProxy()));
549         builder.withNamedArgument("encoding", getEncoding(true));
550         builder.withNamedArgument("p", packageName);
551         builder.withNamedArgument("target", target);
552         builder.withNamedArgument("d", getOutputDirectory().getAbsolutePath());
553         builder.withNamedArgument("classpath", classPath);
554 
555         if (generateEpisode) {
556 
557             // We must use the -extension flag for the episode to work.
558             if (!extension) {
559 
560                 if (getLog().isInfoEnabled()) {
561                     getLog().info("Adding 'extension' flag to XJC arguments, since the 'generateEpisode' argument is "
562                             + "given. (XJCs 'episode' argument requires that the 'extension' argument is provided).");
563                 }
564                 builder.withFlag(true, "extension");
565             }
566 
567             final File episodeFile = getEpisodeFile(episodeFileNameOrNull);
568             builder.withNamedArgument("episode", FileSystemUtilities.getCanonicalPath(episodeFile));
569         }
570         if (catalog != null) {
571             builder.withNamedArgument("catalog", FileSystemUtilities.getCanonicalPath(catalog));
572         }
573 
574         if (arguments != null) {
575             builder.withPreCompiledArguments(arguments);
576         }
577 
578         for (File current : getSourceXJBs()) {
579 
580             // Shorten the argument?
581             // final String strippedXjbPath = FileSystemUtilities.relativize(
582             //         current.getAbsolutePath(), getProject().getBasedir());
583 
584             // Each XJB must be given as a separate argument.
585             builder.withPreCompiledArguments(Arrays.asList("-b", current.getAbsolutePath()));
586         }
587 
588         final List<URL> sourceXSDs = getSources();
589         if (sourceXSDs.isEmpty()) {
590 
591             // If we have no XSDs, we are not going to be able to run XJC.
592             getLog().warn("No XSD files found. Please check your plugin configuration.");
593             throw new NoSchemasException();
594 
595         } else {
596 
597             final List<String> unwrappedSourceXSDs = new ArrayList<String>();
598             for (URL current : sourceXSDs) {
599 
600                 // Shorten the argument if possible.
601                 if ("file".equalsIgnoreCase(current.getProtocol())) {
602                     unwrappedSourceXSDs.add(FileSystemUtilities.relativize(
603                             current.getPath(),
604                             new File(System.getProperty("user.dir"))));
605                 } else {
606                     unwrappedSourceXSDs.add(current.toString());
607                 }
608             }
609 
610             builder.withPreCompiledArguments(unwrappedSourceXSDs);
611         }
612 
613         // All done.
614         return logAndReturnToolArguments(builder.build(), "XJC");
615     }
616 
617     private String getProxyString(final Proxy activeProxy) {
618 
619         // Check sanity
620         if (activeProxy == null) {
621             return null;
622         }
623 
624         // The XJC proxy argument should be on the form
625         // [user[:password]@]proxyHost[:proxyPort]
626         //
627         // builder.withNamedArgument("httpproxy", httpproxy);
628         //
629         final StringBuilder proxyBuilder = new StringBuilder();
630         if (activeProxy.getUsername() != null) {
631 
632             // Start with the username.
633             proxyBuilder.append(activeProxy.getUsername());
634 
635             // Append the password if provided.
636             if (activeProxy.getPassword() != null) {
637                 proxyBuilder.append(":").append(activeProxy.getPassword());
638             }
639 
640             proxyBuilder.append("@");
641         }
642 
643         // Append hostname and port.
644         proxyBuilder.append(activeProxy.getHost()).append(":").append(activeProxy.getPort());
645 
646         // All done.
647         return proxyBuilder.toString();
648     }
649 }