1 package org.codehaus.mojo.jaxb2.schemageneration;
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.jxc.SchemaGenerator;
23 import com.thoughtworks.qdox.JavaProjectBuilder;
24 import com.thoughtworks.qdox.model.JavaClass;
25 import com.thoughtworks.qdox.model.JavaSource;
26 import org.apache.maven.plugin.MojoExecutionException;
27 import org.apache.maven.plugin.MojoFailureException;
28 import org.apache.maven.plugins.annotations.Parameter;
29 import org.codehaus.mojo.jaxb2.AbstractJaxbMojo;
30 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.DefaultJavaDocRenderer;
31 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.JavaDocExtractor;
32 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.JavaDocRenderer;
33 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.SearchableDocumentation;
34 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.schemaenhancement.SimpleNamespaceResolver;
35 import org.codehaus.mojo.jaxb2.schemageneration.postprocessing.schemaenhancement.TransformSchema;
36 import org.codehaus.mojo.jaxb2.shared.FileSystemUtilities;
37 import org.codehaus.mojo.jaxb2.shared.arguments.ArgumentBuilder;
38 import org.codehaus.mojo.jaxb2.shared.environment.ToolExecutionEnvironment;
39 import org.codehaus.mojo.jaxb2.shared.environment.classloading.ThreadContextClassLoaderBuilder;
40 import org.codehaus.mojo.jaxb2.shared.environment.logging.LoggingHandlerEnvironmentFacet;
41 import org.codehaus.mojo.jaxb2.shared.filters.Filter;
42 import org.codehaus.mojo.jaxb2.shared.filters.pattern.PatternFileFilter;
43 import org.codehaus.plexus.util.FileUtils;
44
45 import java.io.File;
46 import java.io.IOException;
47 import java.lang.reflect.InvocationTargetException;
48 import java.net.HttpURLConnection;
49 import java.net.URL;
50 import java.net.URLConnection;
51 import java.util.ArrayList;
52 import java.util.Arrays;
53 import java.util.Collections;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.SortedMap;
57 import java.util.TreeMap;
58 import java.util.regex.Pattern;
59
60 /**
61 * <p>Abstract superclass for Mojos that generate XSD files from annotated Java Sources.
62 * This Mojo delegates execution to the {@code schemagen} tool to perform the XSD file
63 * generation. Moreover, the AbstractXsdGeneratorMojo provides an augmented processing
64 * pipeline by optionally letting a set of NodeProcessors improve the 'vanilla' XSD files.</p>
65 *
66 * @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a>
67 * @see <a href="https://jaxb.java.net/">The JAXB Reference Implementation</a>
68 */
69 public abstract class AbstractXsdGeneratorMojo extends AbstractJaxbMojo {
70
71 /**
72 * <p>Pattern matching the names of files emitted by the JAXB/JDK SchemaGenerator.
73 * According to the JAXB Schema Generator documentation:</p>
74 * <blockquote>There is no way to control the name of the generated schema files at this time.</blockquote>
75 */
76 public static final Pattern SCHEMAGEN_EMITTED_FILENAME = Pattern.compile("schema\\p{javaDigit}+.xsd");
77
78 /**
79 * <p>The default JavaDocRenderer used unless another JavaDocRenderer should be used.</p>
80 *
81 * @see #javaDocRenderer
82 * @since 2.0
83 */
84 public static final JavaDocRenderer STANDARD_JAVADOC_RENDERER = new DefaultJavaDocRenderer();
85
86 /**
87 * Default exclude file name suffixes for testSources, used unless overridden by an
88 * explicit configuration in the {@code testSourceExcludeSuffixes} parameter.
89 */
90 public static final List<Filter<File>> STANDARD_BYTECODE_EXCLUDE_FILTERS;
91
92 /**
93 * Filter list containing a PatternFileFilter including ".class" files.
94 */
95 public static final List<Filter<File>> CLASS_INCLUDE_FILTERS;
96
97 static {
98
99 final List<Filter<File>> schemagenTmp = new ArrayList<Filter<File>>();
100 schemagenTmp.addAll(AbstractJaxbMojo.STANDARD_EXCLUDE_FILTERS);
101 schemagenTmp.add(new PatternFileFilter(Arrays.asList("\\.java", "\\.scala", "\\.mdo"), false));
102 STANDARD_BYTECODE_EXCLUDE_FILTERS = Collections.unmodifiableList(schemagenTmp);
103
104 CLASS_INCLUDE_FILTERS = new ArrayList<Filter<File>>();
105 CLASS_INCLUDE_FILTERS.add(new PatternFileFilter(Arrays.asList("\\.class"), true));
106 }
107
108 // Internal state
109 private static final int SCHEMAGEN_COMPLETED_OK = 0;
110
111 /**
112 * <p>A List holding desired schema mappings, each of which binds a schema namespace URI to its desired prefix
113 * [optional] and the name of the resulting schema file [optional]. All given elements (uri, prefix, file) must be
114 * unique within the configuration; no two elements may have the same values.</p>
115 * <p>The example schema configuration below maps two namespace uris to prefixes and generated file names. This implies
116 * that <tt>http://some/namespace</tt> will be represented by the prefix <tt>some</tt> within the generated XML
117 * Schema files; creating namespace definitions on the form <tt>xmlns:some="http://some/namespace"</tt>, and
118 * corresponding uses on the form <tt><xs:element minOccurs="0"
119 * ref="<strong>some:</strong>anOptionalElementInSomeNamespace"/></tt>. Moreover, the file element defines that the
120 * <tt>http://some/namespace</tt> definitions will be written to the file <tt>some_schema.xsd</tt>, and that all
121 * import references will be on the form <tt><xs:import namespace="http://some/namespace"
122 * schemaLocation="<strong>some_schema.xsd</strong>"/></tt></p>
123 * <p>The example configuration below also performs identical operations for the namespace uri
124 * <tt>http://another/namespace</tt> with the prefix <tt>another</tt> and the file <tt>another_schema.xsd</tt>.
125 * </p>
126 * <pre>
127 * <code>
128 * <transformSchemas>
129 * <transformSchema>
130 * <uri>http://some/namespace</uri>
131 * <toPrefix>some</toPrefix>
132 * <toFile>some_schema.xsd</toFile>
133 * <transformSchema>
134 * <uri>http://another/namespace</uri>
135 * <toPrefix>another</toPrefix>
136 * <toFile>another_schema.xsd</toFile>
137 * </transformSchema>
138 * </transformSchemas>
139 * </code>
140 * </pre>
141 *
142 * @since 1.4
143 */
144 @Parameter
145 private List<TransformSchema> transformSchemas;
146
147 /**
148 * <p>Corresponding SchemaGen parameter: {@code episode}.</p>
149 * <p>Generate an episode file from this XSD generation, so that other schemas that rely on this schema can be
150 * compiled later and rely on classes that are generated from this compilation. The generated episode file is
151 * really just a JAXB customization file (but with vendor extensions.)</p>
152 * <p>If this parameter is {@code true}, the episode file generated is called {@code META-INF/sun-jaxb.episode},
153 * and included in the artifact.</p>
154 *
155 * @see #STANDARD_EPISODE_FILENAME
156 * @since 2.0
157 */
158 @Parameter(defaultValue = "true")
159 protected boolean generateEpisode;
160
161 /**
162 * <p>If {@code true}, Elements or Attributes in the generated XSD files will be annotated with any
163 * JavaDoc found for their respective properties. If {@code false}, no XML documentation annotations will be
164 * generated in post-processing any results from the JAXB SchemaGenerator.</p>
165 *
166 * @since 2.0
167 */
168 @Parameter(defaultValue = "true")
169 protected boolean createJavaDocAnnotations;
170
171 /**
172 * <p>A renderer used to create XML annotation text from JavaDoc comments found within the source code.
173 * Unless another implementation is provided, the standard JavaDocRenderer used is
174 * {@linkplain org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.DefaultJavaDocRenderer}.</p>
175 *
176 * @see org.codehaus.mojo.jaxb2.schemageneration.postprocessing.javadoc.DefaultJavaDocRenderer
177 * @since 2.0
178 */
179 @Parameter
180 protected JavaDocRenderer javaDocRenderer;
181
182 /**
183 * <p>Removes all files from the output directory before running SchemaGenerator.</p>
184 *
185 * @since 2.0
186 */
187 @Parameter(defaultValue = "true")
188 protected boolean clearOutputDir;
189
190 /**
191 * <p>XSD schema files are not generated from POM projects or if no includes have been supplied.</p>
192 * {@inheritDoc}
193 */
194 @Override
195 protected boolean shouldExecutionBeSkipped() {
196
197 boolean toReturn = false;
198
199 if ("pom".equalsIgnoreCase(getProject().getPackaging())) {
200 warnAboutIncorrectPluginConfiguration("packaging", "POM-packaged projects should not generate XSDs.");
201 toReturn = true;
202 }
203
204 if (getSources().isEmpty()) {
205 warnAboutIncorrectPluginConfiguration("sources", "At least one Java Source file has to be included.");
206 toReturn = true;
207 }
208
209 // All done.
210 return toReturn;
211 }
212
213 /**
214 * {@inheritDoc}
215 */
216 @Override
217 protected boolean isReGenerationRequired() {
218
219 //
220 // Use the stale flag method to identify if we should re-generate the XSDs from the sources.
221 // Basically, we should re-generate the XSDs if:
222 //
223 // a) The staleFile does not exist
224 // b) The staleFile exists and is older than one of the sources (Java or XJB files).
225 // "Older" is determined by comparing the modification timestamp of the staleFile and the source files.
226 //
227 final File staleFile = getStaleFile();
228 final String debugPrefix = "StaleFile [" + FileSystemUtilities.getCanonicalPath(staleFile) + "]";
229
230 boolean stale = !staleFile.exists();
231 if (stale) {
232 getLog().debug(debugPrefix + " not found. XML Schema (re-)generation required.");
233 } else {
234
235 final List<URL> sources = getSources();
236
237 if (getLog().isDebugEnabled()) {
238 getLog().debug(debugPrefix + " found. Checking timestamps on source Java "
239 + "files to determine if XML Schema (re-)generation is required.");
240 }
241
242 final long staleFileLastModified = staleFile.lastModified();
243 for (URL current : sources) {
244
245 final URLConnection sourceFileConnection;
246 try {
247 sourceFileConnection = current.openConnection();
248 sourceFileConnection.connect();
249 } catch (Exception e) {
250
251 if (getLog().isDebugEnabled()) {
252 getLog().debug("Could not open a sourceFileConnection to [" + current + "]", e);
253 }
254
255 // Can't determine if the staleFile is younger than this source.
256 // Re-generate to be on the safe side.
257 stale = true;
258 break;
259 }
260
261 try {
262 if (sourceFileConnection.getLastModified() > staleFileLastModified) {
263
264 if (getLog().isDebugEnabled()) {
265 getLog().debug(current.toString() + " is newer than the stale flag file.");
266 }
267 stale = true;
268 }
269 } finally {
270 if (sourceFileConnection instanceof HttpURLConnection) {
271 ((HttpURLConnection) sourceFileConnection).disconnect();
272 }
273 }
274 }
275 }
276
277 // All done.
278 return stale;
279 }
280
281 /**
282 * {@inheritDoc}
283 */
284 @Override
285 protected boolean performExecution() throws MojoExecutionException, MojoFailureException {
286
287 boolean updateStaleFileTimestamp = false;
288 ToolExecutionEnvironment environment = null;
289
290 try {
291
292 // Configure the ThreadContextClassLoaderBuilder, to enable synthesizing a correct ClassPath for the tool.
293 final ThreadContextClassLoaderBuilder classLoaderBuilder = ThreadContextClassLoaderBuilder
294 .createFor(this.getClass(), getLog())
295 .addPaths(getClasspath())
296 .addPaths(getProject().getCompileSourceRoots());
297
298 // Create the execution environment as required by the XJC tool.
299 environment = new ToolExecutionEnvironment(
300 getLog(),
301 classLoaderBuilder,
302 LoggingHandlerEnvironmentFacet.create(getLog(), getClass(), getEncoding(false)));
303 final String projectBasedirPath = FileSystemUtilities.getCanonicalPath(getProject().getBasedir());
304
305 // Setup the environment.
306 environment.setup();
307
308 // Compile the SchemaGen arguments
309 final List<URL> sources = getSources();
310 final String[] schemaGenArguments = getSchemaGenArguments(
311 environment.getClassPathAsArgument(),
312 STANDARD_EPISODE_FILENAME,
313 sources);
314
315 // Ensure that the outputDirectory and workDirectory exists.
316 // Clear them if configured to do so.
317 FileSystemUtilities.createDirectory(getOutputDirectory(), clearOutputDir);
318 FileSystemUtilities.createDirectory(getWorkDirectory(), clearOutputDir);
319
320 // Do we need to re-create the episode file's parent directory.
321 final boolean reCreateEpisodeFileParentDirectory = generateEpisode && clearOutputDir;
322 if (reCreateEpisodeFileParentDirectory) {
323 getEpisodeFile(STANDARD_EPISODE_FILENAME);
324 }
325
326 try {
327
328 // Check the system properties.
329 logSystemPropertiesAndBasedir();
330
331 // Fire the SchemaGenerator
332 final int result = SchemaGenerator.run(
333 schemaGenArguments,
334 Thread.currentThread().getContextClassLoader());
335
336 if (SCHEMAGEN_COMPLETED_OK != result) {
337 printSchemaGenCommandAndThrowException(projectBasedirPath,
338 sources,
339 schemaGenArguments,
340 result,
341 null);
342 }
343
344 // Copy generated XSDs and episode files from the WorkDirectory to the OutputDirectory,
345 // but do not copy the intermediary bytecode files generated by schemagen.
346 final List<Filter<File>> exclusionFilters = PatternFileFilter.createIncludeFilterList(
347 getLog(), "\\.class");
348
349 final List<File> toCopy = FileSystemUtilities.resolveRecursively(
350 Arrays.asList(getWorkDirectory()),
351 exclusionFilters, getLog());
352 for (File current : toCopy) {
353
354 // Get the path to the current file
355 final String currentPath = FileSystemUtilities.getCanonicalPath(current.getAbsoluteFile());
356 final File target = new File(getOutputDirectory(),
357 FileSystemUtilities.relativize(currentPath, getWorkDirectory()));
358
359 // Copy the file to the same relative structure within the output directory.
360 FileSystemUtilities.createDirectory(target.getParentFile(), false);
361 FileUtils.copyFile(current, target);
362 }
363
364 //
365 // The XSD post-processing should be applied in the following order:
366 //
367 // 1. [XsdAnnotationProcessor]: Inject JavaDoc annotations.
368 // 2. [ChangeNamespacePrefixProcessor]: Change namespace prefixes within XSDs.
369 // 3. [ChangeFilenameProcessor]: Change the fileNames of XSDs.
370 //
371
372 final boolean performPostProcessing = createJavaDocAnnotations || transformSchemas != null;
373 if (performPostProcessing) {
374
375 // Map the XML Namespaces to their respective XML URIs (and reverse)
376 // The keys are the generated 'vanilla' XSD file names.
377 final Map<String, SimpleNamespaceResolver> resolverMap =
378 XsdGeneratorHelper.getFileNameToResolverMap(getOutputDirectory());
379
380 if (createJavaDocAnnotations) {
381
382 if (getLog().isInfoEnabled()) {
383 getLog().info("XSD post-processing: Adding JavaDoc annotations in generated XSDs.");
384 }
385
386 // Resolve the sources
387 final List<File> fileSources = new ArrayList<File>();
388 for (URL current : sources) {
389 if ("file".equalsIgnoreCase(current.getProtocol())) {
390 final File toAdd = new File(current.getPath());
391 if (toAdd.exists()) {
392 fileSources.add(toAdd);
393 } else {
394 if (getLog().isWarnEnabled()) {
395 getLog().warn("Ignoring URL [" + current + "] as it is a nonexistent file.");
396 }
397 }
398 }
399 }
400
401 final List<File> files = FileSystemUtilities.resolveRecursively(
402 fileSources, null, getLog());
403
404 // Acquire JavaDocs
405 final JavaDocExtractor extractor = new JavaDocExtractor(getLog()).addSourceFiles(files);
406 final SearchableDocumentation javaDocs = extractor.process();
407
408 // Modify the 'vanilla' generated XSDs by inserting the JavaDoc as annotations
409 final JavaDocRenderer renderer = javaDocRenderer == null
410 ? STANDARD_JAVADOC_RENDERER
411 : javaDocRenderer;
412 final int numProcessedFiles = XsdGeneratorHelper.insertJavaDocAsAnnotations(getLog(),
413 getOutputDirectory(),
414 javaDocs,
415 renderer);
416
417 if (getLog().isDebugEnabled()) {
418 getLog().info("XSD post-processing: " + numProcessedFiles + " files processed.");
419 }
420 }
421
422 if (transformSchemas != null) {
423
424 if (getLog().isInfoEnabled()) {
425 getLog().info("XSD post-processing: Renaming and converting XSDs.");
426 }
427
428 // Transform all namespace prefixes as requested.
429 XsdGeneratorHelper.replaceNamespacePrefixes(resolverMap,
430 transformSchemas,
431 getLog(),
432 getOutputDirectory());
433
434 // Rename all generated schema files as requested.
435 XsdGeneratorHelper.renameGeneratedSchemaFiles(resolverMap,
436 transformSchemas,
437 getLog(),
438 getOutputDirectory());
439 }
440 }
441
442 } catch (MojoExecutionException e) {
443 throw e;
444 } catch (InvocationTargetException e) {
445 printSchemaGenCommandAndThrowException(projectBasedirPath, sources, schemaGenArguments, -1, e.getCause());
446 throw new MojoExecutionException("Exception while running the SchemaGenerator.", e.getCause());
447 } catch (Exception e) {
448 printSchemaGenCommandAndThrowException(projectBasedirPath, sources, schemaGenArguments, -1, e);
449 }
450
451 // Indicate that the output directory was updated.
452 getBuildContext().refresh(getOutputDirectory());
453
454 // Update the modification timestamp of the staleFile.
455 updateStaleFileTimestamp = true;
456
457 } finally {
458
459 // Restore the environment
460 if(environment != null) {
461 environment.restore();
462 }
463 }
464
465 // All done.
466 return updateStaleFileTimestamp;
467 }
468
469 /**
470 * @return The working directory to which the SchemaGenerator should initially copy all its generated files,
471 * including bytecode files, compiled from java sources.
472 */
473 protected abstract File getWorkDirectory();
474
475 /**
476 * Finds a List containing URLs to compiled bytecode files within this Compilation Unit.
477 * Typically this equals the resolved files under the project's build directories, plus any
478 * JAR artifacts found on the classpath.
479 *
480 * @return A non-null List containing URLs to bytecode files within this compilation unit.
481 * Typically this equals the resolved files under the project's build directories, plus any JAR
482 * artifacts found on the classpath.
483 */
484 protected abstract List<URL> getCompiledClassNames();
485
486 /**
487 * Override this method to acquire a List holding all URLs to the SchemaGen Java sources for which this
488 * AbstractXsdGeneratorMojo should generate Xml Schema Descriptor files.
489 *
490 * @return A non-null List holding URLs to sources for the XSD generation.
491 */
492 @Override
493 protected abstract List<URL> getSources();
494
495 //
496 // Private helpers
497 //
498
499 private String[] getSchemaGenArguments(final String classPath,
500 final String episodeFileNameOrNull,
501 final List<URL> sources)
502 throws MojoExecutionException {
503
504 final ArgumentBuilder builder = new ArgumentBuilder();
505
506 // Add all flags on the form '-flagName'
507 // builder.withFlag();
508
509 // Add all arguments on the form '-argumentName argumentValue'
510 // (i.e. in 2 separate elements of the returned String[])
511 builder.withNamedArgument("encoding", getEncoding(true));
512 builder.withNamedArgument("d", getWorkDirectory().getAbsolutePath());
513 builder.withNamedArgument("classpath", classPath);
514
515 if (episodeFileNameOrNull != null) {
516 final File episodeFile = getEpisodeFile(episodeFileNameOrNull);
517 builder.withNamedArgument("episode", FileSystemUtilities.getCanonicalPath(episodeFile));
518 }
519
520 try {
521
522 //
523 // The SchemaGenerator does not support directories as arguments:
524 // "Caused by: java.lang.IllegalArgumentException: directories not supported"
525 // ... implying we must resolve source files in the compilation unit.
526 //
527 // There seems to be two ways of adding sources to the SchemaGen tool:
528 // 1) Using java source files
529 // Define the relative paths to source files, calculated from the System.property "user.dir"
530 // (i.e. *not* the Maven "basedir" property) on the form 'src/main/java/se/west/something/SomeClass.java'.
531 // Sample: javac -d . ../github_jaxb2_plugin/src/it/schemagen-main/src/main/java/se/west/gnat/Foo.java
532 //
533 // 2) Using bytecode files
534 // Define the CLASSPATH to point to build output directories (such as target/classes), and then use
535 // package notation arguments on the form 'se.west.something.SomeClass'.
536 // Sample: schemagen -d . -classpath brat se.west.gnat.Foo
537 //
538 // The jaxb2-maven-plugin uses these two methods in the order given.
539 //
540 builder.withPreCompiledArguments(getSchemaGeneratorSourceFiles(sources));
541 } catch (IOException e) {
542 throw new MojoExecutionException("Could not compile source paths for the SchemaGenerator", e);
543 }
544
545 // All done.
546 return logAndReturnToolArguments(builder.build(), "SchemaGen");
547 }
548
549 /**
550 * <p>The SchemaGenerator does not support directories as arguments, implying we must resolve source
551 * files in the compilation unit. This fact is shown when supplying a directory argument as source, when
552 * the tool emits:
553 * <blockquote>Caused by: java.lang.IllegalArgumentException: directories not supported</blockquote></p>
554 * <p>There seems to be two ways of adding sources to the SchemaGen tool:</p>
555 * <dl>
556 * <dt>1. <strong>Java Source</strong> files</dt>
557 * <dd>Define the relative paths to source files, calculated from the System.property {@code user.dir}
558 * (i.e. <strong>not</strong> the Maven {@code basedir} property) on the form
559 * {@code src/main/java/se/west/something/SomeClass.java}.<br/>
560 * <em>Sample</em>: {@code javac -d . .
561 * ./github_jaxb2_plugin/src/it/schemagen-main/src/main/java/se/west/gnat/Foo.java}</dd>
562 * <dt>2. <strong>Bytecode</strong> files</dt>
563 * <dd>Define the {@code CLASSPATH} to point to build output directories (such as target/classes), and then
564 * use package notation arguments on the form {@code se.west.something.SomeClass}.<br/>
565 * <em>Sample</em>: {@code schemagen -d . -classpath brat se.west.gnat.Foo}</dd>
566 * </dl>
567 * <p>The jaxb2-maven-plugin uses these two methods in the order given</p>
568 *
569 * @param sources The compiled sources (as calculated from the local project's
570 * source paths, {@code getSources()}).
571 * @return A sorted List holding all sources to be used by the SchemaGenerator. According to the SchemaGenerator
572 * documentation, the order in which the source arguments are provided is irrelevant.
573 * The sources are to be rendered as the final (open-ended) argument to the schemagen execution.
574 * @see #getSources()
575 */
576 private List<String> getSchemaGeneratorSourceFiles(final List<URL> sources)
577 throws IOException, MojoExecutionException {
578
579 final SortedMap<String, String> className2SourcePath = new TreeMap<String, String>();
580 final File baseDir = getProject().getBasedir();
581 final File userDir = new File(System.getProperty("user.dir"));
582
583 // 1) Find/add all sources available in the compilation unit.
584 for (URL current : sources) {
585
586 final File sourceCodeFile = FileSystemUtilities.getFileFor(current, getEncoding(false));
587
588 // Calculate the relative path for the current source
589 final String relativePath = FileSystemUtilities.relativize(
590 FileSystemUtilities.getCanonicalPath(sourceCodeFile),
591 userDir);
592
593 if (getLog().isDebugEnabled()) {
594 getLog().debug("SourceCodeFile ["
595 + FileSystemUtilities.getCanonicalPath(sourceCodeFile)
596 + "] and userDir [" + FileSystemUtilities.getCanonicalPath(userDir)
597 + "] ==> relativePath: "
598 + relativePath
599 + ". (baseDir: " + FileSystemUtilities.getCanonicalPath(baseDir) + "]");
600 }
601
602 // Find the Java class(es) within the source.
603 final JavaProjectBuilder builder = new JavaProjectBuilder();
604 builder.addSource(sourceCodeFile);
605 builder.setEncoding(getEncoding(true));
606
607 // Map any found FQCN to the relativized path of its source file.
608 for (JavaSource currentJavaSource : builder.getSources()) {
609 for (JavaClass currentJavaClass : currentJavaSource.getClasses()) {
610
611 final String className = currentJavaClass.getFullyQualifiedName();
612 if (className2SourcePath.containsKey(className)) {
613 if (getLog().isWarnEnabled()) {
614 getLog().warn("Already mapped. Source class [" + className + "] within ["
615 + className2SourcePath.get(className)
616 + "]. Not overwriting with [" + relativePath + "]");
617 }
618 } else {
619 className2SourcePath.put(className, relativePath);
620 }
621 }
622 }
623 }
624
625 /*
626 // 2) Find any bytecode available in the compilation unit, and add its file as a SchemaGen argument.
627 //
628 // The algorithm is:
629 // 1) Add bytecode classpath unless its class is already added in source form.
630 // 2) SchemaGen cannot handle directory arguments, so any bytecode files in classpath directories
631 // must be resolved.
632 // 3) All JARs in the classpath should be added as arguments to SchemaGen.
633 //
634 // .... Gosh ...
635 //
636 for (URL current : getCompiledClassNames()) {
637 getLog().debug(" (compiled ClassName) --> " + current.toExternalForm());
638 }
639
640 Filters.initialize(getLog(), CLASS_INCLUDE_FILTERS);
641
642 final List<URL> classPathURLs = new ArrayList<URL>();
643 for (String current : getClasspath()) {
644
645 final File currentFile = new File(current);
646 if (FileSystemUtilities.EXISTING_FILE.accept(currentFile)) {
647
648 // This is a file/JAR. Simply add its path to SchemaGen's arguments.
649 classPathURLs.add(FileSystemUtilities.getUrlFor(currentFile));
650
651 } else if (FileSystemUtilities.EXISTING_DIRECTORY.accept(currentFile)) {
652
653 // Resolve all bytecode files within this directory.
654 // FileSystemUtilities.filterFiles(baseDir, )
655 if (getLog().isDebugEnabled()) {
656 getLog().debug("TODO: Resolve and add bytecode files within: ["
657 + FileSystemUtilities.getCanonicalPath(currentFile) + "]");
658 }
659
660 // Find the byte code files within the current directory.
661 final List<File> byteCodeFiles = new ArrayList<File>();
662 for(File currentResolvedFile : FileSystemUtilities.resolveRecursively(
663 Arrays.asList(currentFile), null, getLog())) {
664
665 if(Filters.matchAtLeastOnce(currentResolvedFile, CLASS_INCLUDE_FILTERS)) {
666 byteCodeFiles.add(currentResolvedFile);
667 }
668 }
669
670 for(File currentByteCodeFile : byteCodeFiles) {
671
672 final String currentCanonicalPath = FileSystemUtilities.getCanonicalPath(
673 currentByteCodeFile.getAbsoluteFile());
674
675 final String relativized = FileSystemUtilities.relativize(currentCanonicalPath,
676 FileSystemUtilities.getCanonicalFile(currentFile.getAbsoluteFile()));
677 final String pathFromUserDir = FileSystemUtilities.relativize(currentCanonicalPath, userDir);
678
679 final String className = relativized.substring(0, relativized.indexOf(".class"))
680 .replace("/", ".")
681 .replace(File.separator, ".");
682
683 if(!className2SourcePath.containsKey(className)) {
684 className2SourcePath.put(className, pathFromUserDir);
685
686 if(getLog().isDebugEnabled()) {
687 getLog().debug("Adding ByteCode [" + className + "] at relativized path ["
688 + pathFromUserDir + "]");
689 }
690 } else {
691 if(getLog().isDebugEnabled()) {
692 getLog().debug("ByteCode [" + className + "] already added. Not re-adding.");
693 }
694 }
695 }
696
697 } else if (getLog().isWarnEnabled()) {
698
699 final String suffix = !currentFile.exists() ? " nonexistent" : " was neither a File nor a Directory";
700 getLog().warn("Classpath part [" + current + "] " + suffix + ". Ignoring it.");
701 }
702 }
703
704 /*
705 for (URL current : getCompiledClassNames()) {
706
707 // TODO: FIX THIS!
708 // Get the class information data from the supplied URL
709 for (String currentClassPathElement : getClasspath()) {
710
711 if(getLog().isDebugEnabled()) {
712 getLog().debug("Checking class path element: [" + currentClassPathElement + "]");
713 }
714 }
715
716 if(getLog().isDebugEnabled()) {
717 getLog().debug("Processing compiledClassName: [" + current + "]");
718 }
719
720 // Find the Java class(es) within the source.
721 final JavaProjectBuilder builder = new JavaProjectBuilder();
722 builder.setEncoding(getEncoding(true));
723 builder.addSource(current);
724
725 for (JavaSource currentSource : builder.getSources()) {
726 for (JavaClass currentClass : currentSource.getClasses()) {
727
728 final String className = currentClass.getFullyQualifiedName();
729 if (className2SourcePath.containsKey(className)) {
730 if (getLog().isWarnEnabled()) {
731 getLog().warn("Already mapped. Source class [" + className + "] within ["
732 + className2SourcePath.get(className)
733 + "]. Not overwriting with [" + className + "]");
734 }
735 } else {
736 className2SourcePath.put(className, className);
737 }
738 }
739 }
740 }
741 */
742
743 if (getLog().isDebugEnabled()) {
744
745 final int size = className2SourcePath.size();
746 getLog().debug("[ClassName-2-SourcePath Map (size: " + size + ")] ...");
747
748 int i = 0;
749 for (Map.Entry<String, String> current : className2SourcePath.entrySet()) {
750 getLog().debug(" " + (++i) + "/" + size + ": [" + current.getKey() + "]: "
751 + current.getValue());
752 }
753 getLog().debug("... End [ClassName-2-SourcePath Map]");
754 }
755
756 // Sort the source paths and place them first in the argument array
757 final ArrayList<String> toReturn = new ArrayList<String>(className2SourcePath.values());
758 Collections.sort(toReturn);
759
760 // All Done.
761 return toReturn;
762 }
763
764 private void printSchemaGenCommandAndThrowException(final String projectBasedirPath,
765 final List<URL> sources,
766 final String[] schemaGenArguments,
767 final int result,
768 final Throwable cause) throws MojoExecutionException {
769
770 final StringBuilder errorMsgBuilder = new StringBuilder();
771 errorMsgBuilder.append("\n+=================== [SchemaGenerator Error '"
772 + (result == -1 ? "<unknown>" : result) + "']\n");
773 errorMsgBuilder.append("|\n");
774 errorMsgBuilder.append("| SchemaGen did not complete its operation correctly.\n");
775 errorMsgBuilder.append("|\n");
776 errorMsgBuilder.append("| To re-create the error (and get a proper error message), cd to:\n");
777 errorMsgBuilder.append("| ").append(projectBasedirPath).append("\n");
778 errorMsgBuilder.append("| ... and fire the following on a command line/in a shell:\n");
779 errorMsgBuilder.append("|\n");
780
781 final StringBuilder builder = new StringBuilder("schemagen ");
782 for (String current : schemaGenArguments) {
783 builder.append(current).append(" ");
784 }
785
786 errorMsgBuilder.append("| " + builder.toString() + "\n");
787 errorMsgBuilder.append("|\n");
788 errorMsgBuilder.append("| The following source files should be processed by schemagen:\n");
789
790 for (int i = 0; i < sources.size(); i++) {
791 errorMsgBuilder.append("| " + i + ": ").append(sources.get(i).toString()).append("\n");
792 }
793
794 errorMsgBuilder.append("|\n");
795 errorMsgBuilder.append("+=================== [End SchemaGenerator Error]\n");
796
797 if(cause != null) {
798 throw new MojoExecutionException(errorMsgBuilder.toString(), cause);
799 } else {
800 throw new MojoExecutionException(errorMsgBuilder.toString());
801 }
802 }
803 }