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