001/*
002 *   Copyright (C) 2005 Christian Schulte <cs@schulte.it>
003 *   All rights reserved.
004 *
005 *   Redistribution and use in source and binary forms, with or without
006 *   modification, are permitted provided that the following conditions
007 *   are met:
008 *
009 *     o Redistributions of source code must retain the above copyright
010 *       notice, this list of conditions and the following disclaimer.
011 *
012 *     o Redistributions in binary form must reproduce the above copyright
013 *       notice, this list of conditions and the following disclaimer in
014 *       the documentation and/or other materials provided with the
015 *       distribution.
016 *
017 *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
018 *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
019 *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
020 *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
021 *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
022 *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
023 *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
024 *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
026 *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027 *
028 *   $JOMC: ToolsModelProcessor.java 5135 2016-04-08 13:53:07Z schulte $
029 *
030 */
031package org.jomc.tools.modlet;
032
033import java.lang.reflect.Field;
034import java.text.MessageFormat;
035import java.util.Locale;
036import java.util.ResourceBundle;
037import java.util.logging.Level;
038import org.jomc.model.Dependencies;
039import org.jomc.model.Implementation;
040import org.jomc.model.JavaTypeName;
041import org.jomc.model.Messages;
042import org.jomc.model.ModelObjectException;
043import org.jomc.model.Module;
044import org.jomc.model.Modules;
045import org.jomc.model.Properties;
046import org.jomc.model.Specification;
047import org.jomc.model.Specifications;
048import org.jomc.model.modlet.ModelHelper;
049import org.jomc.modlet.Model;
050import org.jomc.modlet.ModelContext;
051import org.jomc.modlet.ModelException;
052import org.jomc.modlet.ModelProcessor;
053import org.jomc.tools.model.SourceFileType;
054import org.jomc.tools.model.SourceFilesType;
055import org.jomc.tools.model.SourceSectionType;
056import org.jomc.tools.model.SourceSectionsType;
057import static org.jomc.tools.modlet.ToolsModletConstants.ANNOTATIONS_SECTION_NAME;
058import static org.jomc.tools.modlet.ToolsModletConstants.CONSTRUCTORS_HEAD_TEMPLATE;
059import static org.jomc.tools.modlet.ToolsModletConstants.CONSTRUCTORS_SECTION_NAME;
060import static org.jomc.tools.modlet.ToolsModletConstants.CONSTRUCTORS_TAIL_TEMPLATE;
061import static org.jomc.tools.modlet.ToolsModletConstants.DEFAULT_CONSTRUCTOR_SECTION_NAME;
062import static org.jomc.tools.modlet.ToolsModletConstants.DEFAULT_CONSTRUCTOR_TEMPLATE;
063import static org.jomc.tools.modlet.ToolsModletConstants.DEPENDENCIES_SECTION_NAME;
064import static org.jomc.tools.modlet.ToolsModletConstants.DEPENDENCIES_TEMPLATE;
065import static org.jomc.tools.modlet.ToolsModletConstants.DOCUMENTATION_SECTION_NAME;
066import static org.jomc.tools.modlet.ToolsModletConstants.IMPLEMENTATION_ANNOTATIONS_TEMPLATE;
067import static org.jomc.tools.modlet.ToolsModletConstants.IMPLEMENTATION_DOCUMENTATION_TEMPLATE;
068import static org.jomc.tools.modlet.ToolsModletConstants.IMPLEMENTATION_LICENSE_TEMPLATE;
069import static org.jomc.tools.modlet.ToolsModletConstants.IMPLEMENTATION_TEMPLATE;
070import static org.jomc.tools.modlet.ToolsModletConstants.LICENSE_SECTION_NAME;
071import static org.jomc.tools.modlet.ToolsModletConstants.MESSAGES_SECTION_NAME;
072import static org.jomc.tools.modlet.ToolsModletConstants.MESSAGES_TEMPLATE;
073import static org.jomc.tools.modlet.ToolsModletConstants.PROPERTIES_SECTION_NAME;
074import static org.jomc.tools.modlet.ToolsModletConstants.PROPERTIES_TEMPLATE;
075import static org.jomc.tools.modlet.ToolsModletConstants.SPECIFICATION_ANNOTATIONS_TEMPLATE;
076import static org.jomc.tools.modlet.ToolsModletConstants.SPECIFICATION_DOCUMENTATION_TEMPLATE;
077import static org.jomc.tools.modlet.ToolsModletConstants.SPECIFICATION_LICENSE_TEMPLATE;
078import static org.jomc.tools.modlet.ToolsModletConstants.SPECIFICATION_TEMPLATE;
079
080/**
081 * Object management and configuration tools {@code ModelProcessor} implementation.
082 *
083 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
084 * @version $JOMC: ToolsModelProcessor.java 5135 2016-04-08 13:53:07Z schulte $
085 * @see ModelContext#processModel(org.jomc.modlet.Model)
086 * @since 1.2
087 */
088public class ToolsModelProcessor implements ModelProcessor
089{
090
091    /**
092     * Constant for the name of the model context attribute backing property {@code enabled}.
093     *
094     * @see #processModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
095     * @see ModelContext#getAttribute(java.lang.String)
096     */
097    public static final String ENABLED_ATTRIBUTE_NAME = "org.jomc.tools.modlet.ToolsModelProcessor.enabledAttribute";
098
099    /**
100     * Constant for the name of the system property controlling property {@code defaultEnabled}.
101     *
102     * @see #isDefaultEnabled()
103     */
104    private static final String DEFAULT_ENABLED_PROPERTY_NAME =
105        "org.jomc.tools.modlet.ToolsModelProcessor.defaultEnabled";
106
107    /**
108     * Default value of the flag indicating the processor is enabled by default.
109     *
110     * @see #isDefaultEnabled()
111     */
112    private static final Boolean DEFAULT_ENABLED = Boolean.TRUE;
113
114    /**
115     * Flag indicating the processor is enabled by default.
116     */
117    private static volatile Boolean defaultEnabled;
118
119    /**
120     * Flag indicating the processor is enabled.
121     */
122    private Boolean enabled;
123
124    /**
125     * Constant for the name of the model context attribute backing property
126     * {@code modelObjectClasspathResolutionEnabled}.
127     *
128     * @see #processModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
129     * @see ModelContext#getAttribute(java.lang.String)
130     */
131    public static final String MODEL_OBJECT_CLASSPATH_RESOLUTION_ENABLED_ATTRIBUTE_NAME =
132        "org.jomc.tools.modlet.ToolsModelProcessor.modelObjectClasspathResolutionEnabledAttribute";
133
134    /**
135     * Constant for the name of the system property controlling property
136     * {@code defaultModelObjectClasspathResolutionEnabled}.
137     *
138     * @see #isDefaultModelObjectClasspathResolutionEnabled()
139     */
140    private static final String DEFAULT_MODEL_OBJECT_CLASSPATH_RESOLUTION_ENABLED_PROPERTY_NAME =
141        "org.jomc.tools.modlet.ToolsModelProcessor.defaultModelObjectClasspathResolutionEnabled";
142
143    /**
144     * Default value of the flag indicating model object class path resolution is enabled by default.
145     *
146     * @see #isDefaultModelObjectClasspathResolutionEnabled()
147     */
148    private static final Boolean DEFAULT_MODEL_OBJECT_CLASSPATH_RESOLUTION_ENABLED = Boolean.TRUE;
149
150    /**
151     * Flag indicating model object class path resolution is enabled by default.
152     */
153    private static volatile Boolean defaultModelObjectClasspathResolutionEnabled;
154
155    /**
156     * Flag indicating model object class path resolution is enabled.
157     */
158    private Boolean modelObjectClasspathResolutionEnabled;
159
160    /**
161     * Constant for the name of the model context attribute backing property {@code headComment}.
162     *
163     * @see #processModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
164     * @see ModelContext#getAttribute(java.lang.String)
165     * @since 1.6
166     */
167    public static final String HEAD_COMMENT_ATTRIBUTE_NAME =
168        "org.jomc.tools.modlet.ToolsModelProcessor.headCommentAttribute";
169
170    /**
171     * Constant for the name of the system property controlling property {@code defaultHeadComment}.
172     *
173     * @see #getDefaultHeadComment()
174     * @since 1.6
175     */
176    private static final String DEFAULT_HEAD_COMMENT_PROPERTY_NAME =
177        "org.jomc.tools.modlet.ToolsModelProcessor.defaultHeadComment";
178
179    /**
180     * Default head comment the processor is applying by default.
181     *
182     * @see #getDefaultHeadComment()
183     * @since 1.6
184     */
185    private static final String DEFAULT_HEAD_COMMENT = "//";
186
187    /**
188     * Head comment the processor is applying by default.
189     *
190     * @since 1.6
191     */
192    private static volatile String defaultHeadComment;
193
194    /**
195     * Head comment the processor is applying.
196     *
197     * @since 1.6
198     */
199    private String headComment;
200
201    /**
202     * Constant for the name of the model context attribute backing property {@code tailComment}.
203     *
204     * @see #processModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
205     * @see ModelContext#getAttribute(java.lang.String)
206     * @since 1.6
207     */
208    public static final String TAIL_COMMENT_ATTRIBUTE_NAME =
209        "org.jomc.tools.modlet.ToolsModelProcessor.tailCommentAttribute";
210
211    /**
212     * Constant for the name of the system property controlling property {@code defaultTailComment}.
213     *
214     * @see #getDefaultTailComment()
215     * @since 1.6
216     */
217    private static final String DEFAULT_TAIL_COMMENT_PROPERTY_NAME =
218        "org.jomc.tools.modlet.ToolsModelProcessor.defaultTailComment";
219
220    /**
221     * Default tail comment the processor is applying by default.
222     *
223     * @see #getDefaultTailComment()
224     * @since 1.6
225     */
226    private static final String DEFAULT_TAIL_COMMENT = null;
227
228    /**
229     * Tail comment the processor is applying by default.
230     *
231     * @since 1.6
232     */
233    private static volatile String defaultTailComment;
234
235    /**
236     * Tail comment the processor is applying.
237     *
238     * @since 1.6
239     */
240    private String tailComment;
241
242    /**
243     * Creates a new {@code ToolsModelProcessor} instance.
244     */
245    public ToolsModelProcessor()
246    {
247        super();
248    }
249
250    /**
251     * Gets a flag indicating the processor is enabled by default.
252     * <p>
253     * The default enabled flag is controlled by system property
254     * {@code org.jomc.tools.modlet.ToolsModelProcessor.defaultEnabled} holding a value indicating the processor is
255     * enabled by default. If that property is not set, the {@code true} default is returned.
256     * </p>
257     *
258     * @return {@code true}, if the processor is enabled by default; {@code false}, if the processor is disabled by
259     * default.
260     *
261     * @see #setDefaultEnabled(java.lang.Boolean)
262     */
263    public static boolean isDefaultEnabled()
264    {
265        if ( defaultEnabled == null )
266        {
267            defaultEnabled = Boolean.valueOf( System.getProperty( DEFAULT_ENABLED_PROPERTY_NAME,
268                                                                  Boolean.toString( DEFAULT_ENABLED ) ) );
269
270        }
271
272        return defaultEnabled;
273    }
274
275    /**
276     * Sets the flag indicating the processor is enabled by default.
277     *
278     * @param value The new value of the flag indicating the processor is enabled by default or {@code null}.
279     *
280     * @see #isDefaultEnabled()
281     */
282    public static void setDefaultEnabled( final Boolean value )
283    {
284        defaultEnabled = value;
285    }
286
287    /**
288     * Gets a flag indicating the processor is enabled.
289     *
290     * @return {@code true}, if the processor is enabled; {@code false}, if the processor is disabled.
291     *
292     * @see #isDefaultEnabled()
293     * @see #setEnabled(java.lang.Boolean)
294     */
295    public final boolean isEnabled()
296    {
297        if ( this.enabled == null )
298        {
299            this.enabled = isDefaultEnabled();
300        }
301
302        return this.enabled;
303    }
304
305    /**
306     * Sets the flag indicating the processor is enabled.
307     *
308     * @param value The new value of the flag indicating the processor is enabled or {@code null}.
309     *
310     * @see #isEnabled()
311     */
312    public final void setEnabled( final Boolean value )
313    {
314        this.enabled = value;
315    }
316
317    /**
318     * Gets a flag indicating model object class path resolution is enabled by default.
319     * <p>
320     * The model object class path resolution default enabled flag is controlled by system property
321     * {@code org.jomc.tools.modlet.ToolsModelProcessor.defaultModelObjectClasspathResolutionEnabled} holding a value
322     * indicating model object class path resolution is enabled by default. If that property is not set, the
323     * {@code true} default is returned.
324     * </p>
325     *
326     * @return {@code true}, if model object class path resolution is enabled by default; {@code false}, if model object
327     * class path resolution is disabled by default.
328     *
329     * @see #setDefaultModelObjectClasspathResolutionEnabled(java.lang.Boolean)
330     */
331    public static boolean isDefaultModelObjectClasspathResolutionEnabled()
332    {
333        if ( defaultModelObjectClasspathResolutionEnabled == null )
334        {
335            defaultModelObjectClasspathResolutionEnabled = Boolean.valueOf( System.getProperty(
336                DEFAULT_MODEL_OBJECT_CLASSPATH_RESOLUTION_ENABLED_PROPERTY_NAME,
337                Boolean.toString( DEFAULT_MODEL_OBJECT_CLASSPATH_RESOLUTION_ENABLED ) ) );
338
339        }
340
341        return defaultModelObjectClasspathResolutionEnabled;
342    }
343
344    /**
345     * Sets the flag indicating model object class path resolution is enabled by default.
346     *
347     * @param value The new value of the flag indicating model object class path resolution is enabled by default or
348     * {@code null}.
349     *
350     * @see #isDefaultModelObjectClasspathResolutionEnabled()
351     */
352    public static void setDefaultModelObjectClasspathResolutionEnabled( final Boolean value )
353    {
354        defaultModelObjectClasspathResolutionEnabled = value;
355    }
356
357    /**
358     * Gets a flag indicating model object class path resolution is enabled.
359     *
360     * @return {@code true}, if model object class path resolution is enabled; {@code false}, if model object class path
361     * resolution is disabled.
362     *
363     * @see #isDefaultModelObjectClasspathResolutionEnabled()
364     * @see #setModelObjectClasspathResolutionEnabled(java.lang.Boolean)
365     */
366    public final boolean isModelObjectClasspathResolutionEnabled()
367    {
368        if ( this.modelObjectClasspathResolutionEnabled == null )
369        {
370            this.modelObjectClasspathResolutionEnabled = isDefaultModelObjectClasspathResolutionEnabled();
371        }
372
373        return this.modelObjectClasspathResolutionEnabled;
374    }
375
376    /**
377     * Sets the flag indicating model object class path resolution is is enabled.
378     *
379     * @param value The new value of the flag indicating model object class path resolution is enabled or {@code null}.
380     *
381     * @see #isModelObjectClasspathResolutionEnabled()
382     */
383    public final void setModelObjectClasspathResolutionEnabled( final Boolean value )
384    {
385        this.modelObjectClasspathResolutionEnabled = value;
386    }
387
388    /**
389     * Gets the head comment the processor is applying by default.
390     * <p>
391     * The default head comment is controlled by system property
392     * {@code org.jomc.tools.modlet.ToolsModelProcessor.defaultHeadComment} holding the head comment the processor is
393     * applying by default. If that property is not set, the {@code //} default is returned.
394     * </p>
395     *
396     * @return The head comment the processor is applying by default or {@code null}.
397     *
398     * @see #setDefaultHeadComment(java.lang.String)
399     * @since 1.6
400     */
401    public static String getDefaultHeadComment()
402    {
403        if ( defaultHeadComment == null )
404        {
405            defaultHeadComment = System.getProperty( DEFAULT_HEAD_COMMENT_PROPERTY_NAME, DEFAULT_HEAD_COMMENT );
406        }
407
408        return defaultHeadComment;
409    }
410
411    /**
412     * Sets the head comment the processor is applying by default.
413     *
414     * @param value The new head comment the processor is applying by default or {@code null}.
415     *
416     * @see #getDefaultHeadComment()
417     * @since 1.6
418     */
419    public static void setDefaultHeadComment( final String value )
420    {
421        defaultHeadComment = value;
422    }
423
424    /**
425     * Gets the head comment the processor is applying.
426     *
427     * @return The head comment the processor is applying or {@code null}.
428     *
429     * @see #getDefaultHeadComment()
430     * @see #setDefaultHeadComment(java.lang.String)
431     * @since 1.6
432     */
433    public final String getHeadComment()
434    {
435        if ( this.headComment == null )
436        {
437            this.headComment = getDefaultHeadComment();
438        }
439
440        return this.headComment;
441    }
442
443    /**
444     * Sets the head comment the processor is applying.
445     *
446     * @param value The new head comment the processor is applying or {@code null}.
447     *
448     * @see #getHeadComment()
449     * @since 1.6
450     */
451    public final void setHeadComment( final String value )
452    {
453        this.headComment = value;
454    }
455
456    /**
457     * Gets the tail comment the processor is applying by default.
458     * <p>
459     * The default tail comment is controlled by system property
460     * {@code org.jomc.tools.modlet.ToolsModelProcessor.defaultTailComment} holding the tail comment the processor is
461     * applying by default. If that property is not set, the {@code null} default is returned.
462     * </p>
463     *
464     * @return The tail comment the processor is applying by default or {@code null}.
465     *
466     * @see #setDefaultTailComment(java.lang.String)
467     * @since 1.6
468     */
469    public static String getDefaultTailComment()
470    {
471        if ( defaultTailComment == null )
472        {
473            defaultTailComment = System.getProperty( DEFAULT_TAIL_COMMENT_PROPERTY_NAME, DEFAULT_TAIL_COMMENT );
474        }
475
476        return defaultTailComment;
477    }
478
479    /**
480     * Sets the tail comment the processor is applying by default.
481     *
482     * @param value The new tail comment the processor is applying by default or {@code null}.
483     *
484     * @see #getDefaultTailComment()
485     * @since 1.6
486     */
487    public static void setDefaultTailComment( final String value )
488    {
489        defaultTailComment = value;
490    }
491
492    /**
493     * Gets the tail comment the processor is applying.
494     *
495     * @return The tail comment the processor is applying or {@code null}.
496     *
497     * @see #getDefaultTailComment()
498     * @see #setDefaultTailComment(java.lang.String)
499     * @since 1.6
500     */
501    public final String getTailComment()
502    {
503        if ( this.tailComment == null )
504        {
505            this.tailComment = getDefaultTailComment();
506        }
507
508        return this.tailComment;
509    }
510
511    /**
512     * Sets the tail comment the processor is applying.
513     *
514     * @param value The new tail comment the processor is applying or {@code null}.
515     *
516     * @see #getTailComment()
517     * @since 1.6
518     */
519    public final void setTailComment( final String value )
520    {
521        this.tailComment = value;
522    }
523
524    /**
525     * {@inheritDoc}
526     *
527     * @see #isEnabled()
528     * @see #isModelObjectClasspathResolutionEnabled()
529     * @see #getHeadComment()
530     * @see #getTailComment()
531     * @see #ENABLED_ATTRIBUTE_NAME
532     * @see #MODEL_OBJECT_CLASSPATH_RESOLUTION_ENABLED_ATTRIBUTE_NAME
533     * @see #HEAD_COMMENT_ATTRIBUTE_NAME
534     * @see #TAIL_COMMENT_ATTRIBUTE_NAME
535     */
536    public Model processModel( final ModelContext context, final Model model ) throws ModelException
537    {
538        if ( context == null )
539        {
540            throw new NullPointerException( "context" );
541        }
542        if ( model == null )
543        {
544            throw new NullPointerException( "model" );
545        }
546
547        Model processed = model;
548
549        boolean contextEnabled = this.isEnabled();
550        if ( DEFAULT_ENABLED == contextEnabled && context.getAttribute( ENABLED_ATTRIBUTE_NAME ) instanceof Boolean )
551        {
552            contextEnabled = (Boolean) context.getAttribute( ENABLED_ATTRIBUTE_NAME );
553        }
554
555        boolean contextModelObjectClasspathResolutionEnabled = this.isModelObjectClasspathResolutionEnabled();
556        if ( contextModelObjectClasspathResolutionEnabled == DEFAULT_MODEL_OBJECT_CLASSPATH_RESOLUTION_ENABLED
557                 && context.getAttribute( MODEL_OBJECT_CLASSPATH_RESOLUTION_ENABLED_ATTRIBUTE_NAME ) instanceof Boolean )
558        {
559            contextModelObjectClasspathResolutionEnabled =
560                (Boolean) context.getAttribute( MODEL_OBJECT_CLASSPATH_RESOLUTION_ENABLED_ATTRIBUTE_NAME );
561
562        }
563
564        if ( contextEnabled )
565        {
566            processed = model.clone();
567            final Modules modules = ModelHelper.getModules( processed );
568
569            if ( modules != null )
570            {
571                Module classpathModule = null;
572                if ( contextModelObjectClasspathResolutionEnabled )
573                {
574                    classpathModule = modules.getClasspathModule( Modules.getDefaultClasspathModuleName(),
575                                                                  context.getClassLoader() );
576
577                    if ( classpathModule != null
578                             && modules.getModule( Modules.getDefaultClasspathModuleName() ) == null )
579                    {
580                        modules.getModule().add( classpathModule );
581                    }
582                    else
583                    {
584                        classpathModule = null;
585                    }
586                }
587
588                if ( modules.getSpecifications() != null )
589                {
590                    for ( int i = 0, s0 = modules.getSpecifications().getSpecification().size(); i < s0; i++ )
591                    {
592                        final Specification specification = modules.getSpecifications().getSpecification().get( i );
593                        final SourceFileType sourceFileType = specification.getAnyObject( SourceFileType.class );
594                        final SourceFilesType sourceFilesType = specification.getAnyObject( SourceFilesType.class );
595
596                        if ( sourceFileType != null )
597                        {
598                            if ( sourceFileType.getLocation() == null && specification.getClazz() != null )
599                            {
600                                // As of 1.2, the 'location' attribute got updated from 'required' to 'optional'.
601                                sourceFileType.setLocation( new StringBuilder( specification.getClazz().length() + 5 ).
602                                    append( specification.getClazz().replace( '.', '/' ) ).append( ".java" ).
603                                    toString() );
604
605                            }
606
607                            if ( sourceFileType.getHeadComment() == null )
608                            {
609                                // As of 1.2, the 'head-comment' and 'tail-comment' attributes got introduced.
610                                sourceFileType.setHeadComment( "//" );
611                            }
612                        }
613
614                        if ( sourceFilesType != null )
615                        {
616                            this.applyDefaults( context, modules, specification, sourceFilesType );
617                        }
618                    }
619                }
620
621                if ( modules.getImplementations() != null )
622                {
623                    for ( int i = 0, s0 = modules.getImplementations().getImplementation().size(); i < s0; i++ )
624                    {
625                        final Implementation implementation = modules.getImplementations().getImplementation().get( i );
626                        final SourceFileType sourceFileType = implementation.getAnyObject( SourceFileType.class );
627                        final SourceFilesType sourceFilesType = implementation.getAnyObject( SourceFilesType.class );
628
629                        if ( sourceFileType != null )
630                        {
631                            if ( sourceFileType.getLocation() == null && implementation.getClazz() != null )
632                            {
633                                // As of 1.2, the 'location' attribute got updated from 'required' to 'optional'.
634                                sourceFileType.setLocation( new StringBuilder( implementation.getClazz().length() + 5 ).
635                                    append( implementation.getClazz().replace( '.', '/' ) ).append( ".java" ).
636                                    toString() );
637
638                            }
639
640                            if ( sourceFileType.getHeadComment() == null )
641                            {
642                                // As of 1.2, the 'head-comment' and 'tail-comment' attributes got introduced.
643                                sourceFileType.setHeadComment( "//" );
644                            }
645                        }
646
647                        if ( sourceFilesType != null )
648                        {
649                            this.applyDefaults( context, modules, implementation, sourceFilesType );
650                        }
651                    }
652                }
653
654                if ( classpathModule != null )
655                {
656                    modules.getModule().remove( classpathModule );
657                }
658            }
659        }
660        else if ( context.isLoggable( Level.FINER ) )
661        {
662            context.log( Level.FINER, getMessage( "disabled", this.getClass().getSimpleName(),
663                                                  model.getIdentifier() ), null );
664
665        }
666
667        return processed;
668    }
669
670    /**
671     * Gets the default source code file location for a given specification.
672     * <p>
673     * If the specification provides a Java type name, this method returns a Java source code file location based on
674     * that Java type name.
675     * </p>
676     *
677     * @param context The context to get the default location with.
678     * @param modules The model to get the default location with.
679     * @param specification The specification to get the default location for.
680     *
681     * @return The default location for {@code specification} or {@code null}.
682     *
683     * @throws NullPointerException if {@code context}, {@code modules} or {@code specification} is {@code null}.
684     *
685     * @see SourceFileType#getLocation()
686     * @see Specification#getJavaTypeName()
687     * @since 1.6
688     */
689    protected String getDefaultSourceFileLocation( final ModelContext context, final Modules modules,
690                                                   final Specification specification )
691    {
692        if ( context == null )
693        {
694            throw new NullPointerException( "context" );
695        }
696        if ( modules == null )
697        {
698            throw new NullPointerException( "modules" );
699        }
700        if ( specification == null )
701        {
702            throw new NullPointerException( "specification" );
703        }
704
705        String location = null;
706
707        try
708        {
709            if ( specification.getJavaTypeName() != null )
710            {
711                location = specification.getJavaTypeName().getQualifiedName().replace( '.', '/' ) + ".java";
712            }
713        }
714        catch ( final ModelObjectException e )
715        {
716            context.log( Level.WARNING, getMessage( e ), null );
717        }
718
719        return location;
720    }
721
722    /**
723     * Gets the default source code file location for a given implementation.
724     * <p>
725     * If the implementation provides a Java type name, this method returns a Java source code file location based on
726     * that Java type name.
727     * </p>
728     *
729     * @param context The context to get the default location with.
730     * @param modules The model to get the default location with.
731     * @param implementation The implementation to get the default location for.
732     *
733     * @return The default location for {@code implementation} or {@code null}.
734     *
735     * @throws NullPointerException if {@code context}, {@code modules} or {@code implementation} is {@code null}.
736     *
737     * @see SourceFileType#getLocation()
738     * @see Implementation#getJavaTypeName()
739     * @since 1.6
740     */
741    protected String getDefaultSourceFileLocation( final ModelContext context, final Modules modules,
742                                                   final Implementation implementation )
743    {
744        if ( context == null )
745        {
746            throw new NullPointerException( "context" );
747        }
748        if ( modules == null )
749        {
750            throw new NullPointerException( "modules" );
751        }
752        if ( implementation == null )
753        {
754            throw new NullPointerException( "implementation" );
755        }
756
757        String location = null;
758
759        try
760        {
761            if ( implementation.getJavaTypeName() != null )
762            {
763                location = implementation.getJavaTypeName().getQualifiedName().replace( '.', '/' ) + ".java";
764            }
765        }
766        catch ( final ModelObjectException e )
767        {
768            context.log( Level.WARNING, getMessage( e ), null );
769        }
770
771        return location;
772    }
773
774    /**
775     * Gets the default source section name for a given specification.
776     * <p>
777     * If the specification provides a Java type name, this method returns a section name based on that Java type
778     * name.
779     * </p>
780     *
781     * @param context The context to get the default section name with.
782     * @param modules The model to get the default section name with.
783     * @param specification The specification to get the default section name for.
784     *
785     * @return The default source section name for {@code specification} or {@code null}.
786     *
787     * @throws NullPointerException if {@code context}, {@code modules} or {@code specification} is {@code null}.
788     *
789     * @see SourceSectionType#getName()
790     * @see Specification#getJavaTypeName()
791     * @since 1.6
792     */
793    protected String getDefaultSourceSectionName( final ModelContext context, final Modules modules,
794                                                  final Specification specification )
795    {
796        if ( context == null )
797        {
798            throw new NullPointerException( "context" );
799        }
800        if ( modules == null )
801        {
802            throw new NullPointerException( "modules" );
803        }
804        if ( specification == null )
805        {
806            throw new NullPointerException( "specification" );
807        }
808
809        String sectionName = null;
810
811        try
812        {
813            final JavaTypeName javaTypeName = specification.getJavaTypeName();
814
815            if ( javaTypeName != null )
816            {
817                sectionName = javaTypeName.getName( false );
818            }
819        }
820        catch ( final ModelObjectException e )
821        {
822            context.log( Level.WARNING, getMessage( e ), null );
823        }
824
825        return sectionName;
826    }
827
828    /**
829     * Gets the default source section name for a given implementation.
830     * <p>
831     * If the implementation provides a Java type name, this method returns a section name based that Java type
832     * name.
833     * </p>
834     *
835     * @param context The context to get the default section name with.
836     * @param modules The model to get the default section name with.
837     * @param implementation The implementation to get the default section name for.
838     *
839     * @return The default source section name for {@code implementation} or {@code null}.
840     *
841     * @throws NullPointerException if {@code context}, {@code modules} or {@code implementation} is {@code null}.
842     *
843     * @see SourceSectionType#getName()
844     * @see Implementation#getJavaTypeName()
845     * @since 1.6
846     */
847    protected String getDefaultSourceSectionName( final ModelContext context, final Modules modules,
848                                                  final Implementation implementation )
849    {
850        if ( context == null )
851        {
852            throw new NullPointerException( "context" );
853        }
854        if ( modules == null )
855        {
856            throw new NullPointerException( "modules" );
857        }
858        if ( implementation == null )
859        {
860            throw new NullPointerException( "implementation" );
861        }
862
863        String sectionName = null;
864
865        try
866        {
867            final JavaTypeName javaTypeName = implementation.getJavaTypeName();
868
869            if ( javaTypeName != null )
870            {
871                sectionName = javaTypeName.getName( false );
872            }
873        }
874        catch ( final ModelObjectException e )
875        {
876            context.log( Level.WARNING, getMessage( e ), null );
877        }
878
879        return sectionName;
880    }
881
882    /**
883     * Updates any optional attributes to default values.
884     *
885     * @param context The context to apply defaults with.
886     * @param modules The model to to apply defaults with.
887     * @param specification The specification corresponding to {@code sourceFilesType}.
888     * @param sourceFilesType The model to update.
889     *
890     * @throws NullPointerException if {@code context}, {@code modules}, {@code specification} or
891     * {@code sourceFilesType} is {@code null}.
892     */
893    private void applyDefaults( final ModelContext context, final Modules modules, final Specification specification,
894                                final SourceFilesType sourceFilesType )
895    {
896        if ( context == null )
897        {
898            throw new NullPointerException( "context" );
899        }
900        if ( modules == null )
901        {
902            throw new NullPointerException( "modules" );
903        }
904        if ( specification == null )
905        {
906            throw new NullPointerException( "specification" );
907        }
908        if ( sourceFilesType == null )
909        {
910            throw new NullPointerException( "sourceFilesType" );
911        }
912
913        String contextHeadComment = this.getHeadComment();
914        if ( ( DEFAULT_HEAD_COMMENT != null
915               ? DEFAULT_HEAD_COMMENT.equals( contextHeadComment )
916               : contextHeadComment == null )
917                 && context.getAttribute( HEAD_COMMENT_ATTRIBUTE_NAME ) instanceof String )
918        {
919            contextHeadComment = (String) context.getAttribute( HEAD_COMMENT_ATTRIBUTE_NAME );
920        }
921
922        if ( contextHeadComment != null && contextHeadComment.length() == 0 )
923        {
924            contextHeadComment = null;
925        }
926
927        String contextTailComment = this.getTailComment();
928        if ( ( DEFAULT_TAIL_COMMENT != null
929               ? DEFAULT_TAIL_COMMENT.equals( contextTailComment )
930               : contextTailComment == null )
931                 && context.getAttribute( TAIL_COMMENT_ATTRIBUTE_NAME ) instanceof String )
932        {
933            contextTailComment = (String) context.getAttribute( TAIL_COMMENT_ATTRIBUTE_NAME );
934        }
935
936        if ( contextTailComment != null && contextTailComment.length() == 0 )
937        {
938            contextTailComment = null;
939        }
940
941        for ( int i = 0, s0 = sourceFilesType.getSourceFile().size(); i < s0; i++ )
942        {
943            final SourceFileType s = sourceFilesType.getSourceFile().get( i );
944
945            if ( s.getTemplate() == null )
946            {
947                s.setTemplate( SPECIFICATION_TEMPLATE );
948            }
949            if ( s.getLocation() == null )
950            {
951                s.setLocation( this.getDefaultSourceFileLocation( context, modules, specification ) );
952            }
953            if ( s.getHeadComment() == null )
954            {
955                s.setHeadComment( contextHeadComment );
956            }
957            if ( s.getTailComment() == null )
958            {
959                s.setTailComment( contextTailComment );
960            }
961
962            this.applyDefaults( context, modules, specification, s.getSourceSections() );
963        }
964    }
965
966    /**
967     * Updates any optional attributes to default values.
968     *
969     * @param context The context to apply defaults with.
970     * @param modules The model to to apply defaults with.
971     * @param specification The specification corresponding to {@code sourceSectionsType}.
972     * @param sourceSectionsType The model to update or {@code null}.
973     *
974     * @throws NullPointerException if {@code context}, {@code modules} or {@code specification} is {@code null}.
975     */
976    private void applyDefaults( final ModelContext context, final Modules modules, final Specification specification,
977                                final SourceSectionsType sourceSectionsType )
978    {
979        if ( context == null )
980        {
981            throw new NullPointerException( "context" );
982        }
983        if ( modules == null )
984        {
985            throw new NullPointerException( "modules" );
986        }
987        if ( specification == null )
988        {
989            throw new NullPointerException( "specification" );
990        }
991
992        try
993        {
994            if ( sourceSectionsType != null )
995            {
996                for ( int i = 0, s0 = sourceSectionsType.getSourceSection().size(); i < s0; i++ )
997                {
998                    final SourceSectionType s = sourceSectionsType.getSourceSection().get( i );
999
1000                    if ( LICENSE_SECTION_NAME.equals( s.getName() ) )
1001                    {
1002                        if ( !isFieldSet( s, "optional" ) )
1003                        {
1004                            s.setOptional( true );
1005                        }
1006                        if ( s.getHeadTemplate() == null )
1007                        {
1008                            s.setHeadTemplate( SPECIFICATION_LICENSE_TEMPLATE );
1009                        }
1010                    }
1011
1012                    if ( ANNOTATIONS_SECTION_NAME.equals( s.getName() ) )
1013                    {
1014                        if ( s.getHeadTemplate() == null )
1015                        {
1016                            s.setHeadTemplate( SPECIFICATION_ANNOTATIONS_TEMPLATE );
1017                        }
1018                    }
1019
1020                    if ( DOCUMENTATION_SECTION_NAME.equals( s.getName() ) )
1021                    {
1022                        if ( !isFieldSet( s, "optional" ) )
1023                        {
1024                            s.setOptional( true );
1025                        }
1026                        if ( s.getHeadTemplate() == null )
1027                        {
1028                            s.setHeadTemplate( SPECIFICATION_DOCUMENTATION_TEMPLATE );
1029                        }
1030                    }
1031
1032                    final String sectionName = this.getDefaultSourceSectionName( context, modules, specification );
1033
1034                    if ( sectionName != null && sectionName.equals( s.getName() ) )
1035                    {
1036                        if ( !isFieldSet( s, "editable" ) )
1037                        {
1038                            s.setEditable( true );
1039                        }
1040                        if ( !isFieldSet( s, "indentationLevel" ) )
1041                        {
1042                            s.setIndentationLevel( 1 );
1043                        }
1044                    }
1045
1046                    this.applyDefaults( context, modules, specification, s.getSourceSections() );
1047                }
1048            }
1049        }
1050        catch ( final NoSuchFieldException e )
1051        {
1052            throw new AssertionError( e );
1053        }
1054    }
1055
1056    /**
1057     * Updates any optional attributes to default values.
1058     *
1059     * @param context The context to apply defaults with.
1060     * @param modules The model to to apply defaults with.
1061     * @param implementation The implementation corresponding to {@code sourceFilesType}.
1062     * @param sourceFilesType The model to update.
1063     *
1064     * @throws NullPointerException if {@code context}, {@code modules}, {@code implementation} or
1065     * {@code sourceFilesType} is {@code null}.
1066     */
1067    private void applyDefaults( final ModelContext context, final Modules modules, final Implementation implementation,
1068                                final SourceFilesType sourceFilesType )
1069    {
1070        if ( context == null )
1071        {
1072            throw new NullPointerException( "context" );
1073        }
1074        if ( modules == null )
1075        {
1076            throw new NullPointerException( "modules" );
1077        }
1078        if ( implementation == null )
1079        {
1080            throw new NullPointerException( "implementation" );
1081        }
1082        if ( sourceFilesType == null )
1083        {
1084            throw new NullPointerException( "sourceFilesType" );
1085        }
1086
1087        String contextHeadComment = this.getHeadComment();
1088        if ( ( DEFAULT_HEAD_COMMENT != null
1089               ? DEFAULT_HEAD_COMMENT.equals( contextHeadComment )
1090               : contextHeadComment == null )
1091                 && context.getAttribute( HEAD_COMMENT_ATTRIBUTE_NAME ) instanceof String )
1092        {
1093            contextHeadComment = (String) context.getAttribute( HEAD_COMMENT_ATTRIBUTE_NAME );
1094        }
1095
1096        if ( contextHeadComment != null && contextHeadComment.length() == 0 )
1097        {
1098            contextHeadComment = null;
1099        }
1100
1101        String contextTailComment = this.getTailComment();
1102        if ( ( DEFAULT_TAIL_COMMENT != null
1103               ? DEFAULT_TAIL_COMMENT.equals( contextTailComment )
1104               : contextTailComment == null )
1105                 && context.getAttribute( TAIL_COMMENT_ATTRIBUTE_NAME ) instanceof String )
1106        {
1107            contextTailComment = (String) context.getAttribute( TAIL_COMMENT_ATTRIBUTE_NAME );
1108        }
1109
1110        if ( contextTailComment != null && contextTailComment.length() == 0 )
1111        {
1112            contextTailComment = null;
1113        }
1114
1115        for ( int i = 0, s0 = sourceFilesType.getSourceFile().size(); i < s0; i++ )
1116        {
1117            final SourceFileType s = sourceFilesType.getSourceFile().get( i );
1118
1119            if ( s.getTemplate() == null )
1120            {
1121                s.setTemplate( IMPLEMENTATION_TEMPLATE );
1122            }
1123            if ( s.getLocation() == null )
1124            {
1125                s.setLocation( this.getDefaultSourceFileLocation( context, modules, implementation ) );
1126            }
1127            if ( s.getHeadComment() == null )
1128            {
1129                s.setHeadComment( contextHeadComment );
1130            }
1131            if ( s.getTailComment() == null )
1132            {
1133                s.setTailComment( contextTailComment );
1134            }
1135
1136            this.applyDefaults( context, modules, implementation, s.getSourceSections() );
1137        }
1138    }
1139
1140    /**
1141     * Updates any optional attributes to default values.
1142     *
1143     * @param context The context to apply defaults with.
1144     * @param modules The model to to apply defaults with.
1145     * @param implementation The implementation corresponding to {@code sourceSectionsType}.
1146     * @param sourceSectionsType The model to update or {@code null}.
1147     *
1148     * @throws NullPointerException if {@code context}, {@code modules} or {@code implementation} is {@code null}.
1149     */
1150    private void applyDefaults( final ModelContext context, final Modules modules, final Implementation implementation,
1151                                final SourceSectionsType sourceSectionsType )
1152    {
1153        if ( context == null )
1154        {
1155            throw new NullPointerException( "context" );
1156        }
1157        if ( modules == null )
1158        {
1159            throw new NullPointerException( "modules" );
1160        }
1161        if ( implementation == null )
1162        {
1163            throw new NullPointerException( "implementation" );
1164        }
1165
1166        final Specifications specifications = modules.getSpecifications( implementation.getIdentifier() );
1167        final Dependencies dependencies = modules.getDependencies( implementation.getIdentifier() );
1168        final Messages messages = modules.getMessages( implementation.getIdentifier() );
1169        final Properties properties = modules.getProperties( implementation.getIdentifier() );
1170
1171        try
1172        {
1173            if ( sourceSectionsType != null )
1174            {
1175                for ( int i = 0, s0 = sourceSectionsType.getSourceSection().size(); i < s0; i++ )
1176                {
1177                    final SourceSectionType s = sourceSectionsType.getSourceSection().get( i );
1178
1179                    if ( LICENSE_SECTION_NAME.equals( s.getName() ) )
1180                    {
1181                        if ( !isFieldSet( s, "optional" ) )
1182                        {
1183                            s.setOptional( true );
1184                        }
1185                        if ( s.getHeadTemplate() == null )
1186                        {
1187                            s.setHeadTemplate( IMPLEMENTATION_LICENSE_TEMPLATE );
1188                        }
1189                    }
1190
1191                    if ( ANNOTATIONS_SECTION_NAME.equals( s.getName() ) )
1192                    {
1193                        if ( s.getHeadTemplate() == null )
1194                        {
1195                            s.setHeadTemplate( IMPLEMENTATION_ANNOTATIONS_TEMPLATE );
1196                        }
1197                    }
1198
1199                    if ( DOCUMENTATION_SECTION_NAME.equals( s.getName() ) )
1200                    {
1201                        if ( !isFieldSet( s, "optional" ) )
1202                        {
1203                            s.setOptional( true );
1204                        }
1205                        if ( s.getHeadTemplate() == null )
1206                        {
1207                            s.setHeadTemplate( IMPLEMENTATION_DOCUMENTATION_TEMPLATE );
1208                        }
1209                    }
1210
1211                    if ( CONSTRUCTORS_SECTION_NAME.equals( s.getName() ) )
1212                    {
1213                        if ( !isFieldSet( s, "indentationLevel" ) )
1214                        {
1215                            s.setIndentationLevel( 1 );
1216                        }
1217                        if ( s.getHeadTemplate() == null )
1218                        {
1219                            s.setHeadTemplate( CONSTRUCTORS_HEAD_TEMPLATE );
1220                        }
1221                        if ( s.getTailTemplate() == null )
1222                        {
1223                            s.setTailTemplate( CONSTRUCTORS_TAIL_TEMPLATE );
1224                        }
1225                        if ( !isFieldSet( s, "optional" ) )
1226                        {
1227                            s.setOptional( specifications == null || ( specifications.getSpecification().isEmpty()
1228                                                                       && specifications.getReference().isEmpty() ) );
1229
1230                        }
1231                    }
1232
1233                    if ( DEFAULT_CONSTRUCTOR_SECTION_NAME.equals( s.getName() ) )
1234                    {
1235                        if ( !isFieldSet( s, "editable" ) )
1236                        {
1237                            s.setEditable( true );
1238                        }
1239                        if ( !isFieldSet( s, "indentationLevel" ) )
1240                        {
1241                            s.setIndentationLevel( 2 );
1242                        }
1243                        if ( s.getHeadTemplate() == null )
1244                        {
1245                            s.setHeadTemplate( DEFAULT_CONSTRUCTOR_TEMPLATE );
1246                        }
1247                    }
1248
1249                    if ( DEPENDENCIES_SECTION_NAME.equals( s.getName() ) )
1250                    {
1251                        if ( !isFieldSet( s, "optional" ) )
1252                        {
1253                            s.setOptional( dependencies == null || dependencies.getDependency().isEmpty() );
1254                        }
1255                        if ( !isFieldSet( s, "indentationLevel" ) )
1256                        {
1257                            s.setIndentationLevel( 1 );
1258                        }
1259                        if ( s.getHeadTemplate() == null )
1260                        {
1261                            s.setHeadTemplate( DEPENDENCIES_TEMPLATE );
1262                        }
1263                    }
1264
1265                    if ( PROPERTIES_SECTION_NAME.equals( s.getName() ) )
1266                    {
1267                        if ( !isFieldSet( s, "optional" ) )
1268                        {
1269                            s.setOptional( properties == null || properties.getProperty().isEmpty() );
1270                        }
1271                        if ( !isFieldSet( s, "indentationLevel" ) )
1272                        {
1273                            s.setIndentationLevel( 1 );
1274                        }
1275                        if ( s.getHeadTemplate() == null )
1276                        {
1277                            s.setHeadTemplate( PROPERTIES_TEMPLATE );
1278                        }
1279                    }
1280
1281                    if ( MESSAGES_SECTION_NAME.equals( s.getName() ) )
1282                    {
1283                        if ( !isFieldSet( s, "optional" ) )
1284                        {
1285                            s.setOptional( messages == null || messages.getMessage().isEmpty() );
1286                        }
1287                        if ( !isFieldSet( s, "indentationLevel" ) )
1288                        {
1289                            s.setIndentationLevel( 1 );
1290                        }
1291                        if ( s.getHeadTemplate() == null )
1292                        {
1293                            s.setHeadTemplate( MESSAGES_TEMPLATE );
1294                        }
1295                    }
1296
1297                    if ( specifications != null )
1298                    {
1299                        for ( final Specification specification : specifications.getSpecification() )
1300                        {
1301                            final String sectionName =
1302                                this.getDefaultSourceSectionName( context, modules, specification );
1303
1304                            if ( sectionName != null && sectionName.equals( s.getName() ) )
1305                            {
1306                                if ( !isFieldSet( s, "editable" ) )
1307                                {
1308                                    s.setEditable( true );
1309                                }
1310                                if ( !isFieldSet( s, "indentationLevel" ) )
1311                                {
1312                                    s.setIndentationLevel( 1 );
1313                                }
1314                            }
1315                        }
1316                    }
1317
1318                    final String sectionName = this.getDefaultSourceSectionName( context, modules, implementation );
1319
1320                    if ( sectionName != null && sectionName.equals( s.getName() ) )
1321                    {
1322                        if ( !isFieldSet( s, "editable" ) )
1323                        {
1324                            s.setEditable( true );
1325                        }
1326                        if ( !isFieldSet( s, "indentationLevel" ) )
1327                        {
1328                            s.setIndentationLevel( 1 );
1329                        }
1330                    }
1331
1332                    this.applyDefaults( context, modules, implementation, s.getSourceSections() );
1333                }
1334            }
1335        }
1336        catch ( final NoSuchFieldException e )
1337        {
1338            throw new AssertionError( e );
1339        }
1340    }
1341
1342    private static boolean isFieldSet( final Object object, final String fieldName ) throws NoSuchFieldException
1343    {
1344        final Field field = object.getClass().getDeclaredField( fieldName );
1345        final boolean accessible = field.isAccessible();
1346
1347        try
1348        {
1349            field.setAccessible( true );
1350            return field.get( object ) != null;
1351        }
1352        catch ( final IllegalAccessException e )
1353        {
1354            throw new AssertionError( e );
1355        }
1356        finally
1357        {
1358            field.setAccessible( accessible );
1359        }
1360    }
1361
1362    private static String getMessage( final Throwable t )
1363    {
1364        return t != null
1365                   ? t.getMessage() != null && t.getMessage().trim().length() > 0
1366                         ? t.getMessage()
1367                         : getMessage( t.getCause() )
1368                   : null;
1369
1370    }
1371
1372    private static String getMessage( final String key, final Object... args )
1373    {
1374        return MessageFormat.format( ResourceBundle.getBundle(
1375            ToolsModelProcessor.class.getName().replace( '.', '/' ), Locale.getDefault() ).getString( key ), args );
1376
1377    }
1378
1379}