001/*
002 *   Copyright (C) Christian Schulte, 2005-206
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: ToolsModelProvider.java 4760 2013-04-08 17:56:26Z schulte $
029 *
030 */
031package org.jomc.tools.modlet;
032
033import java.lang.reflect.Field;
034import java.text.MessageFormat;
035import java.util.ArrayList;
036import java.util.HashMap;
037import java.util.List;
038import java.util.Locale;
039import java.util.Map;
040import java.util.ResourceBundle;
041import java.util.Set;
042import java.util.logging.Level;
043import javax.xml.bind.JAXBElement;
044import javax.xml.namespace.QName;
045import org.jomc.model.Dependencies;
046import org.jomc.model.Implementation;
047import org.jomc.model.InheritanceModel;
048import org.jomc.model.JavaTypeName;
049import org.jomc.model.Messages;
050import org.jomc.model.ModelObjectException;
051import org.jomc.model.Module;
052import org.jomc.model.Modules;
053import org.jomc.model.Properties;
054import org.jomc.model.Specification;
055import org.jomc.model.Specifications;
056import org.jomc.model.modlet.ModelHelper;
057import org.jomc.modlet.Model;
058import org.jomc.modlet.ModelContext;
059import org.jomc.modlet.ModelException;
060import org.jomc.modlet.ModelProvider;
061import org.jomc.tools.model.ObjectFactory;
062import org.jomc.tools.model.SourceFileType;
063import org.jomc.tools.model.SourceFilesType;
064import org.jomc.tools.model.SourceSectionType;
065import org.jomc.tools.model.SourceSectionsType;
066import static org.jomc.tools.modlet.ToolsModletConstants.*;
067
068/**
069 * Object management and configuration tools {@code ModelProvider} implementation.
070 *
071 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
072 * @version $JOMC: ToolsModelProvider.java 4760 2013-04-08 17:56:26Z schulte $
073 * @see ModelContext#findModel(java.lang.String)
074 * @since 1.2
075 */
076public class ToolsModelProvider implements ModelProvider
077{
078
079    /** Constant for the qualified name of {@code source-files} elements. */
080    private static final QName SOURCE_FILES_QNAME = new ObjectFactory().createSourceFiles( null ).getName();
081
082    /**
083     * Constant for the name of the model context attribute backing property {@code enabled}.
084     * @see #findModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
085     * @see ModelContext#getAttribute(java.lang.String)
086     */
087    public static final String ENABLED_ATTRIBUTE_NAME = "org.jomc.tools.modlet.ToolsModelProvider.enabledAttribute";
088
089    /**
090     * Constant for the name of the system property controlling property {@code defaultEnabled}.
091     * @see #isDefaultEnabled()
092     */
093    private static final String DEFAULT_ENABLED_PROPERTY_NAME =
094        "org.jomc.tools.modlet.ToolsModelProvider.defaultEnabled";
095
096    /**
097     * Default value of the flag indicating the provider is enabled by default.
098     * @see #isDefaultEnabled()
099     */
100    private static final Boolean DEFAULT_ENABLED = Boolean.TRUE;
101
102    /** Flag indicating the provider is enabled by default. */
103    private static volatile Boolean defaultEnabled;
104
105    /** Flag indicating the provider is enabled. */
106    private Boolean enabled;
107
108    /**
109     * Constant for the name of the model context attribute backing property
110     * {@code modelObjectClasspathResolutionEnabled}.
111     *
112     * @see #findModel(org.jomc.modlet.ModelContext, org.jomc.modlet.Model)
113     * @see ModelContext#getAttribute(java.lang.String)
114     */
115    public static final String MODEL_OBJECT_CLASSPATH_RESOLUTION_ENABLED_ATTRIBUTE_NAME =
116        "org.jomc.tools.modlet.ToolsModelProvider.modelObjectClasspathResolutionEnabledAttribute";
117
118    /**
119     * Constant for the name of the system property controlling property
120     * {@code defaultModelObjectClasspathResolutionEnabled}.
121     * @see #isDefaultModelObjectClasspathResolutionEnabled()
122     */
123    private static final String DEFAULT_MODEL_OBJECT_CLASSPATH_RESOLUTION_ENABLED_PROPERTY_NAME =
124        "org.jomc.tools.modlet.ToolsModelProvider.defaultModelObjectClasspathResolutionEnabled";
125
126    /**
127     * Default value of the flag indicating model object class path resolution is enabled by default.
128     * @see #isDefaultModelObjectClasspathResolutionEnabled()
129     */
130    private static final Boolean DEFAULT_MODEL_OBJECT_CLASSPATH_RESOLUTION_ENABLED = Boolean.TRUE;
131
132    /** Flag indicating model object class path resolution is enabled by default. */
133    private static volatile Boolean defaultModelObjectClasspathResolutionEnabled;
134
135    /** Flag indicating model object class path resolution is enabled. */
136    private Boolean modelObjectClasspathResolutionEnabled;
137
138    /** Creates a new {@code ToolsModelProvider} instance. */
139    public ToolsModelProvider()
140    {
141        super();
142    }
143
144    /**
145     * Gets a flag indicating the provider is enabled by default.
146     * <p>The default enabled flag is controlled by system property
147     * {@code org.jomc.tools.modlet.ToolsModelProvider.defaultEnabled} holding a value indicating the provider is
148     * enabled by default. If that property is not set, the {@code true} default is returned.</p>
149     *
150     * @return {@code true}, if the provider is enabled by default; {@code false}, if the provider is disabled by
151     * default.
152     *
153     * @see #setDefaultEnabled(java.lang.Boolean)
154     */
155    public static boolean isDefaultEnabled()
156    {
157        if ( defaultEnabled == null )
158        {
159            defaultEnabled = Boolean.valueOf( System.getProperty( DEFAULT_ENABLED_PROPERTY_NAME,
160                                                                  Boolean.toString( DEFAULT_ENABLED ) ) );
161
162        }
163
164        return defaultEnabled;
165    }
166
167    /**
168     * Sets the flag indicating the provider is enabled by default.
169     *
170     * @param value The new value of the flag indicating the provider is enabled by default or {@code null}.
171     *
172     * @see #isDefaultEnabled()
173     */
174    public static void setDefaultEnabled( final Boolean value )
175    {
176        defaultEnabled = value;
177    }
178
179    /**
180     * Gets a flag indicating the provider is enabled.
181     *
182     * @return {@code true}, if the provider is enabled; {@code false}, if the provider is disabled.
183     *
184     * @see #isDefaultEnabled()
185     * @see #setEnabled(java.lang.Boolean)
186     */
187    public final boolean isEnabled()
188    {
189        if ( this.enabled == null )
190        {
191            this.enabled = isDefaultEnabled();
192        }
193
194        return this.enabled;
195    }
196
197    /**
198     * Sets the flag indicating the provider is enabled.
199     *
200     * @param value The new value of the flag indicating the provider is enabled or {@code null}.
201     *
202     * @see #isEnabled()
203     */
204    public final void setEnabled( final Boolean value )
205    {
206        this.enabled = value;
207    }
208
209    /**
210     * Gets a flag indicating model object class path resolution is enabled by default.
211     * <p>The model object class path resolution default enabled flag is controlled by system property
212     * {@code org.jomc.tools.modlet.ToolsModelProvider.defaultModelObjectClasspathResolutionEnabled} holding a value
213     * indicating model object class path resolution is enabled by default. If that property is not set, the
214     * {@code true} default is returned.</p>
215     *
216     * @return {@code true}, if model object class path resolution is enabled by default; {@code false}, if model object
217     * class path resolution is disabled by default.
218     *
219     * @see #setDefaultModelObjectClasspathResolutionEnabled(java.lang.Boolean)
220     */
221    public static boolean isDefaultModelObjectClasspathResolutionEnabled()
222    {
223        if ( defaultModelObjectClasspathResolutionEnabled == null )
224        {
225            defaultModelObjectClasspathResolutionEnabled = Boolean.valueOf( System.getProperty(
226                DEFAULT_MODEL_OBJECT_CLASSPATH_RESOLUTION_ENABLED_PROPERTY_NAME,
227                Boolean.toString( DEFAULT_MODEL_OBJECT_CLASSPATH_RESOLUTION_ENABLED ) ) );
228
229        }
230
231        return defaultModelObjectClasspathResolutionEnabled;
232    }
233
234    /**
235     * Sets the flag indicating model object class path resolution is enabled by default.
236     *
237     * @param value The new value of the flag indicating model object class path resolution is enabled by default or
238     * {@code null}.
239     *
240     * @see #isDefaultModelObjectClasspathResolutionEnabled()
241     */
242    public static void setDefaultModelObjectClasspathResolutionEnabled( final Boolean value )
243    {
244        defaultModelObjectClasspathResolutionEnabled = value;
245    }
246
247    /**
248     * Gets a flag indicating model object class path resolution is enabled.
249     *
250     * @return {@code true}, if model object class path resolution is enabled; {@code false}, if model object class path
251     * resolution is disabled.
252     *
253     * @see #isDefaultModelObjectClasspathResolutionEnabled()
254     * @see #setModelObjectClasspathResolutionEnabled(java.lang.Boolean)
255     */
256    public final boolean isModelObjectClasspathResolutionEnabled()
257    {
258        if ( this.modelObjectClasspathResolutionEnabled == null )
259        {
260            this.modelObjectClasspathResolutionEnabled = isDefaultModelObjectClasspathResolutionEnabled();
261        }
262
263        return this.modelObjectClasspathResolutionEnabled;
264    }
265
266    /**
267     * Sets the flag indicating model object class path resolution is is enabled.
268     *
269     * @param value The new value of the flag indicating model object class path resolution is enabled or {@code null}.
270     *
271     * @see #isModelObjectClasspathResolutionEnabled()
272     */
273    public final void setModelObjectClasspathResolutionEnabled( final Boolean value )
274    {
275        this.modelObjectClasspathResolutionEnabled = value;
276    }
277
278    /**
279     * {@inheritDoc}
280     *
281     * @see #isEnabled()
282     * @see #isModelObjectClasspathResolutionEnabled()
283     * @see #ENABLED_ATTRIBUTE_NAME
284     * @see #MODEL_OBJECT_CLASSPATH_RESOLUTION_ENABLED_ATTRIBUTE_NAME
285     */
286    public Model findModel( final ModelContext context, final Model model ) throws ModelException
287    {
288        if ( context == null )
289        {
290            throw new NullPointerException( "context" );
291        }
292        if ( model == null )
293        {
294            throw new NullPointerException( "model" );
295        }
296
297        Model provided = null;
298
299        boolean contextEnabled = this.isEnabled();
300        if ( DEFAULT_ENABLED == contextEnabled && context.getAttribute( ENABLED_ATTRIBUTE_NAME ) instanceof Boolean )
301        {
302            contextEnabled = (Boolean) context.getAttribute( ENABLED_ATTRIBUTE_NAME );
303        }
304
305        boolean contextModelObjectClasspathResolutionEnabled = this.isModelObjectClasspathResolutionEnabled();
306        if ( contextModelObjectClasspathResolutionEnabled == DEFAULT_MODEL_OBJECT_CLASSPATH_RESOLUTION_ENABLED
307             && context.getAttribute( MODEL_OBJECT_CLASSPATH_RESOLUTION_ENABLED_ATTRIBUTE_NAME ) instanceof Boolean )
308        {
309            contextModelObjectClasspathResolutionEnabled =
310                (Boolean) context.getAttribute( MODEL_OBJECT_CLASSPATH_RESOLUTION_ENABLED_ATTRIBUTE_NAME );
311
312        }
313
314        if ( contextEnabled )
315        {
316            provided = model.clone();
317            final Modules modules = ModelHelper.getModules( provided );
318
319            if ( modules != null )
320            {
321                Module classpathModule = null;
322                if ( contextModelObjectClasspathResolutionEnabled )
323                {
324                    classpathModule = modules.getClasspathModule( Modules.getDefaultClasspathModuleName(),
325                                                                  context.getClassLoader() );
326
327                    if ( classpathModule != null
328                         && modules.getModule( Modules.getDefaultClasspathModuleName() ) == null )
329                    {
330                        modules.getModule().add( classpathModule );
331                    }
332                    else
333                    {
334                        classpathModule = null;
335                    }
336                }
337
338                if ( modules.getSpecifications() != null )
339                {
340                    for ( int i = 0, s0 = modules.getSpecifications().getSpecification().size(); i < s0; i++ )
341                    {
342                        final Specification specification = modules.getSpecifications().getSpecification().get( i );
343                        final SourceFileType sourceFileType = specification.getAnyObject( SourceFileType.class );
344                        final SourceFilesType sourceFilesType = specification.getAnyObject( SourceFilesType.class );
345
346                        if ( sourceFileType == null && specification.isClassDeclaration() )
347                        {
348                            final SourceFilesType defaultSourceFiles =
349                                this.getDefaultSourceFilesType( context, modules, specification );
350
351                            if ( sourceFilesType != null )
352                            {
353                                this.overwriteSourceFiles( sourceFilesType, defaultSourceFiles, true );
354                            }
355                            else
356                            {
357                                specification.getAny().add( new ObjectFactory().createSourceFiles(
358                                    this.getDefaultSourceFilesType( context, modules, specification ) ) );
359
360                            }
361                        }
362                    }
363                }
364
365                if ( modules.getImplementations() != null )
366                {
367                    final Map<Implementation, SourceFilesType> userSourceFiles =
368                        new HashMap<Implementation, SourceFilesType>();
369
370                    InheritanceModel imodel = new InheritanceModel( modules );
371
372                    for ( int i = 0, s0 = modules.getImplementations().getImplementation().size(); i < s0; i++ )
373                    {
374                        final Implementation implementation = modules.getImplementations().getImplementation().get( i );
375                        final SourceFileType sourceFileType = implementation.getAnyObject( SourceFileType.class );
376                        final SourceFilesType sourceFilesType = implementation.getAnyObject( SourceFilesType.class );
377
378                        if ( sourceFileType == null )
379                        {
380                            if ( sourceFilesType != null )
381                            {
382                                userSourceFiles.put( implementation, sourceFilesType );
383                            }
384                            else if ( implementation.isClassDeclaration() )
385                            {
386                                final SourceFilesType defaultSourceFiles =
387                                    this.getDefaultSourceFilesType( context, modules, implementation );
388
389                                boolean finalAncestor = false;
390
391                                final Set<InheritanceModel.Node<JAXBElement<?>>> sourceFilesNodes =
392                                    imodel.getJaxbElementNodes( implementation.getIdentifier(), SOURCE_FILES_QNAME );
393
394                                for ( final InheritanceModel.Node<JAXBElement<?>> sourceFilesNode : sourceFilesNodes )
395                                {
396                                    if ( sourceFilesNode.getModelObject().getValue() instanceof SourceFilesType )
397                                    {
398                                        final SourceFilesType ancestorSourceFiles =
399                                            (SourceFilesType) sourceFilesNode.getModelObject().getValue();
400
401                                        this.overwriteSourceFiles( defaultSourceFiles, ancestorSourceFiles, false );
402
403                                        if ( ancestorSourceFiles.isFinal() )
404                                        {
405                                            finalAncestor = true;
406                                        }
407                                    }
408                                }
409
410                                if ( !finalAncestor )
411                                {
412                                    implementation.getAny().add(
413                                        new ObjectFactory().createSourceFiles( defaultSourceFiles ) );
414
415                                }
416                            }
417                        }
418                    }
419
420                    for ( final Map.Entry<Implementation, SourceFilesType> e : userSourceFiles.entrySet() )
421                    {
422                        this.overwriteSourceFiles(
423                            e.getValue(), this.getDefaultSourceFilesType( context, modules, e.getKey() ), true );
424
425                    }
426
427                    imodel = new InheritanceModel( modules );
428
429                    for ( int i = 0, s0 = modules.getImplementations().getImplementation().size(); i < s0; i++ )
430                    {
431                        final Implementation implementation = modules.getImplementations().getImplementation().get( i );
432                        final SourceFilesType sourceFilesType = implementation.getAnyObject( SourceFilesType.class );
433
434                        if ( sourceFilesType != null && !userSourceFiles.containsKey( implementation ) )
435                        {
436                            boolean override = false;
437
438                            final Set<InheritanceModel.Node<JAXBElement<?>>> sourceFilesNodes =
439                                imodel.getJaxbElementNodes( implementation.getIdentifier(), SOURCE_FILES_QNAME );
440
441                            for ( final InheritanceModel.Node<JAXBElement<?>> e : sourceFilesNodes )
442                            {
443                                if ( !e.getOverriddenNodes().isEmpty() )
444                                {
445                                    override = true;
446                                    break;
447                                }
448                            }
449
450                            if ( override )
451                            {
452                                sourceFilesType.setOverride( override );
453                            }
454                        }
455                    }
456                }
457
458                if ( classpathModule != null )
459                {
460                    modules.getModule().remove( classpathModule );
461                }
462            }
463        }
464        else if ( context.isLoggable( Level.FINER ) )
465        {
466            context.log( Level.FINER, getMessage( "disabled", this.getClass().getSimpleName(),
467                                                  model.getIdentifier() ), null );
468
469        }
470
471        return provided;
472    }
473
474    /**
475     * Creates a new default source files model for a given specification.
476     *
477     * @param context The context to create a new default source files model with.
478     * @param modules The model to create a new default source files model with.
479     * @param specification The specification to create a new default source files model for.
480     *
481     * @return A new default source files model for {@code specification}.
482     *
483     * @throws NullPointerExeption if {@code context}, {@code modules} or {@code specification} is {@code null}.
484     */
485    private SourceFilesType getDefaultSourceFilesType( final ModelContext context, final Modules modules,
486                                                       final Specification specification )
487    {
488        if ( context == null )
489        {
490            throw new NullPointerException( "context" );
491        }
492        if ( modules == null )
493        {
494            throw new NullPointerException( "modules" );
495        }
496        if ( specification == null )
497        {
498            throw new NullPointerException( "specification" );
499        }
500
501        final SourceFilesType sourceFilesType = new SourceFilesType();
502        final SourceFileType sourceFileType = new SourceFileType();
503        sourceFilesType.getSourceFile().add( sourceFileType );
504
505        sourceFileType.setIdentifier( "Default" );
506
507        try
508        {
509            if ( specification.getJavaTypeName() != null )
510            {
511                sourceFileType.setLocation(
512                    specification.getJavaTypeName().getQualifiedName().replace( '.', '/' ) + ".java" );
513
514            }
515        }
516        catch ( final ModelObjectException e )
517        {
518            context.log( Level.WARNING, getMessage( e ), null );
519        }
520
521        sourceFileType.setTemplate( SPECIFICATION_TEMPLATE );
522        sourceFileType.setHeadComment( "//" );
523        sourceFileType.setSourceSections( new SourceSectionsType() );
524
525        SourceSectionType s = new SourceSectionType();
526        s.setName( LICENSE_SECTION_NAME );
527        s.setHeadTemplate( SPECIFICATION_LICENSE_TEMPLATE );
528        s.setOptional( true );
529        sourceFileType.getSourceSections().getSourceSection().add( s );
530
531        s = new SourceSectionType();
532        s.setName( ANNOTATIONS_SECTION_NAME );
533        s.setHeadTemplate( SPECIFICATION_ANNOTATIONS_TEMPLATE );
534        sourceFileType.getSourceSections().getSourceSection().add( s );
535
536        s = new SourceSectionType();
537        s.setName( DOCUMENTATION_SECTION_NAME );
538        s.setHeadTemplate( SPECIFICATION_DOCUMENTATION_TEMPLATE );
539        s.setOptional( true );
540        sourceFileType.getSourceSections().getSourceSection().add( s );
541
542        try
543        {
544            final JavaTypeName javaTypeName = specification.getJavaTypeName();
545
546            if ( javaTypeName != null )
547            {
548                s = new SourceSectionType();
549                s.setName( javaTypeName.getName( false ) );
550                s.setIndentationLevel( 1 );
551                s.setEditable( true );
552                sourceFileType.getSourceSections().getSourceSection().add( s );
553            }
554        }
555        catch ( final ModelObjectException e )
556        {
557            context.log( Level.WARNING, getMessage( e ), null );
558        }
559
560        return sourceFilesType;
561    }
562
563    /**
564     * Creates a new default source files model for a given implementation.
565     *
566     * @param context The context to create a new default source files model with.
567     * @param modules The model to create a new default source files model with.
568     * @param implementation The implementation to create a new default source files model for.
569     *
570     * @return A new default source files model for {@code implementation}.
571     *
572     * @throws NullPointerExeption if {@code context}, {@code modules} or {@code implementation} is {@code null}.
573     */
574    private SourceFilesType getDefaultSourceFilesType( final ModelContext context, final Modules modules,
575                                                       final Implementation implementation )
576    {
577        if ( context == null )
578        {
579            throw new NullPointerException( "context" );
580        }
581        if ( modules == null )
582        {
583            throw new NullPointerException( "modules" );
584        }
585        if ( implementation == null )
586        {
587            throw new NullPointerException( "implementation" );
588        }
589
590        final SourceFilesType sourceFilesType = new SourceFilesType();
591        final SourceFileType sourceFileType = new SourceFileType();
592        sourceFilesType.getSourceFile().add( sourceFileType );
593
594        final Specifications specifications = modules.getSpecifications( implementation.getIdentifier() );
595        final Dependencies dependencies = modules.getDependencies( implementation.getIdentifier() );
596        final Messages messages = modules.getMessages( implementation.getIdentifier() );
597        final Properties properties = modules.getProperties( implementation.getIdentifier() );
598
599        sourceFileType.setIdentifier( "Default" );
600
601        try
602        {
603            if ( implementation.getJavaTypeName() != null )
604            {
605                sourceFileType.setLocation(
606                    implementation.getJavaTypeName().getQualifiedName().replace( '.', '/' ) + ".java" );
607
608            }
609        }
610        catch ( final ModelObjectException e )
611        {
612            context.log( Level.WARNING, getMessage( e ), null );
613        }
614
615        sourceFileType.setTemplate( IMPLEMENTATION_TEMPLATE );
616        sourceFileType.setHeadComment( "//" );
617        sourceFileType.setSourceSections( new SourceSectionsType() );
618
619        SourceSectionType s = new SourceSectionType();
620        s.setName( LICENSE_SECTION_NAME );
621        s.setHeadTemplate( IMPLEMENTATION_LICENSE_TEMPLATE );
622        s.setOptional( true );
623        sourceFileType.getSourceSections().getSourceSection().add( s );
624
625        s = new SourceSectionType();
626        s.setName( ANNOTATIONS_SECTION_NAME );
627        s.setHeadTemplate( IMPLEMENTATION_ANNOTATIONS_TEMPLATE );
628        sourceFileType.getSourceSections().getSourceSection().add( s );
629
630        s = new SourceSectionType();
631        s.setName( DOCUMENTATION_SECTION_NAME );
632        s.setHeadTemplate( IMPLEMENTATION_DOCUMENTATION_TEMPLATE );
633        s.setOptional( true );
634        sourceFileType.getSourceSections().getSourceSection().add( s );
635
636        List<JavaTypeName> javaTypeNames = null;
637
638        if ( specifications != null )
639        {
640            javaTypeNames = new ArrayList<JavaTypeName>( specifications.getSpecification().size() );
641
642            for ( final Specification specification : specifications.getSpecification() )
643            {
644                try
645                {
646                    final JavaTypeName javaTypeName = specification.getJavaTypeName();
647
648                    if ( javaTypeName != null && !javaTypeNames.contains( javaTypeName ) )
649                    {
650                        javaTypeNames.add( javaTypeName );
651                    }
652                }
653                catch ( final ModelObjectException e )
654                {
655                    context.log( Level.WARNING, getMessage( e ), null );
656                }
657            }
658
659            for ( int i = 0, s0 = javaTypeNames.size(); i < s0; i++ )
660            {
661                s = new SourceSectionType();
662                s.setName( javaTypeNames.get( i ).getName( false ) );
663                s.setIndentationLevel( 1 );
664                s.setEditable( true );
665                sourceFileType.getSourceSections().getSourceSection().add( s );
666            }
667        }
668
669        try
670        {
671            final JavaTypeName javaTypeName = implementation.getJavaTypeName();
672
673            if ( javaTypeName != null && ( javaTypeNames == null || !javaTypeNames.contains( javaTypeName ) ) )
674            {
675                s = new SourceSectionType();
676                s.setName( javaTypeName.getName( false ) );
677                s.setIndentationLevel( 1 );
678                s.setEditable( true );
679                sourceFileType.getSourceSections().getSourceSection().add( s );
680            }
681        }
682        catch ( final ModelObjectException e )
683        {
684            context.log( Level.WARNING, getMessage( e ), null );
685        }
686
687        s = new SourceSectionType();
688        s.setName( CONSTRUCTORS_SECTION_NAME );
689        s.setIndentationLevel( 1 );
690        s.setHeadTemplate( CONSTRUCTORS_HEAD_TEMPLATE );
691        s.setTailTemplate( CONSTRUCTORS_TAIL_TEMPLATE );
692        s.setOptional( specifications == null || ( specifications.getSpecification().isEmpty()
693                                                   && specifications.getReference().isEmpty() ) );
694
695        s.setSourceSections( new SourceSectionsType() );
696        sourceFileType.getSourceSections().getSourceSection().add( s );
697
698        final SourceSectionType defaultCtor = new SourceSectionType();
699        defaultCtor.setName( DEFAULT_CONSTRUCTOR_SECTION_NAME );
700        defaultCtor.setIndentationLevel( 2 );
701        defaultCtor.setHeadTemplate( DEFAULT_CONSTRUCTOR_TEMPLATE );
702        defaultCtor.setEditable( true );
703        s.getSourceSections().getSourceSection().add( defaultCtor );
704
705        s = new SourceSectionType();
706        s.setName( DEPENDENCIES_SECTION_NAME );
707        s.setIndentationLevel( 1 );
708        s.setHeadTemplate( DEPENDENCIES_TEMPLATE );
709        s.setOptional( dependencies == null || dependencies.getDependency().isEmpty() );
710        sourceFileType.getSourceSections().getSourceSection().add( s );
711
712        s = new SourceSectionType();
713        s.setName( PROPERTIES_SECTION_NAME );
714        s.setIndentationLevel( 1 );
715        s.setHeadTemplate( PROPERTIES_TEMPLATE );
716        s.setOptional( properties == null || properties.getProperty().isEmpty() );
717        sourceFileType.getSourceSections().getSourceSection().add( s );
718
719        s = new SourceSectionType();
720        s.setName( MESSAGES_SECTION_NAME );
721        s.setIndentationLevel( 1 );
722        s.setHeadTemplate( MESSAGES_TEMPLATE );
723        s.setOptional( messages == null || messages.getMessage().isEmpty() );
724        sourceFileType.getSourceSections().getSourceSection().add( s );
725
726        return sourceFilesType;
727    }
728
729    /**
730     * Overwrites a list of source code files with another list of source code files.
731     *
732     * @param targetSourceFiles The list to overwrite.
733     * @param sourceSourceFiles The list to overwrite with.
734     * @param preserveExisting {@code true}, to preserve existing attributes of source code files and sections;
735     * {@code false}, to overwrite existing attributes of source code files and sections.
736     *
737     * @throws NullPointerException if {@code targetSourceFiles} or {@code sourceSourceFiles} is {@code null}.
738     */
739    private void overwriteSourceFiles( final SourceFilesType targetSourceFiles, final SourceFilesType sourceSourceFiles,
740                                       final boolean preserveExisting )
741    {
742        if ( targetSourceFiles == null )
743        {
744            throw new NullPointerException( "targetSourceFiles" );
745        }
746        if ( sourceSourceFiles == null )
747        {
748            throw new NullPointerException( "sourceSourceFiles" );
749        }
750
751        try
752        {
753            for ( final SourceFileType s : sourceSourceFiles.getSourceFile() )
754            {
755                final SourceFileType targetSourceFile = targetSourceFiles.getSourceFile( s.getIdentifier() );
756
757                if ( targetSourceFile != null )
758                {
759                    this.overwriteSourceFile( targetSourceFile, s, preserveExisting );
760                }
761            }
762        }
763        catch ( final NoSuchFieldException e )
764        {
765            throw new AssertionError( e );
766        }
767    }
768
769    /**
770     * Overwrites a source code file with another source code file.
771     *
772     * @param targetSourceFile The source code file to overwrite.
773     * @param sourceSourceFile The source code file to overwrite with.
774     * @param preserveExisting {@code true}, to preserve existing attributes of the given source code file and sections;
775     * {@code false}, to overwrite existing attributes of the given source code file and sections.
776     *
777     * @throws NullPointerException if {@code targetSourceFile} or {@code sourceSourceFile} is {@code null}.
778     */
779    private void overwriteSourceFile( final SourceFileType targetSourceFile, final SourceFileType sourceSourceFile,
780                                      final boolean preserveExisting )
781        throws NoSuchFieldException
782    {
783        if ( targetSourceFile == null )
784        {
785            throw new NullPointerException( "targetSourceFile" );
786        }
787        if ( sourceSourceFile == null )
788        {
789            throw new NullPointerException( "sourceSourceFile" );
790        }
791
792        if ( !preserveExisting )
793        {
794            targetSourceFile.setIdentifier( sourceSourceFile.getIdentifier() );
795            targetSourceFile.setLocation( sourceSourceFile.getLocation() );
796            targetSourceFile.setTemplate( sourceSourceFile.getTemplate() );
797            targetSourceFile.setHeadComment( sourceSourceFile.getHeadComment() );
798            targetSourceFile.setTailComment( sourceSourceFile.getTailComment() );
799
800            if ( isFieldSet( sourceSourceFile, "_final" ) )
801            {
802                targetSourceFile.setFinal( sourceSourceFile.isFinal() );
803            }
804            if ( isFieldSet( sourceSourceFile, "modelVersion" ) )
805            {
806                targetSourceFile.setModelVersion( sourceSourceFile.getModelVersion() );
807            }
808            if ( isFieldSet( sourceSourceFile, "override" ) )
809            {
810                targetSourceFile.setOverride( sourceSourceFile.isOverride() );
811            }
812        }
813
814        if ( sourceSourceFile.getSourceSections() != null )
815        {
816            if ( targetSourceFile.getSourceSections() == null )
817            {
818                targetSourceFile.setSourceSections( new SourceSectionsType() );
819            }
820
821            this.overwriteSourceSections( targetSourceFile.getSourceSections(), sourceSourceFile.getSourceSections(),
822                                          preserveExisting );
823
824        }
825    }
826
827    /**
828     * Overwrites source code file sections with other source code file sections.
829     *
830     * @param targetSourceSections The source code file sections to overwrite.
831     * @param sourceSourceSections The source code file sections to overwrite with.
832     * @param preserveExisting {@code true}, to preserve existing attributes of the given source code file sections;
833     * {@code false}, to overwrite existing attributes of the given source code file sections.
834     *
835     * @throws NullPointerException if {@code targetSourceSections} or {@code sourceSourceSections} is {@code null}.
836     */
837    private void overwriteSourceSections( final SourceSectionsType targetSourceSections,
838                                          final SourceSectionsType sourceSourceSections,
839                                          final boolean preserveExisting ) throws NoSuchFieldException
840    {
841        if ( targetSourceSections == null )
842        {
843            throw new NullPointerException( "targetSourceSections" );
844        }
845        if ( sourceSourceSections == null )
846        {
847            throw new NullPointerException( "sourceSourceSections" );
848        }
849
850        for ( final SourceSectionType sourceSection : sourceSourceSections.getSourceSection() )
851        {
852            SourceSectionType targetSection = null;
853
854            for ( final SourceSectionType t : targetSourceSections.getSourceSection() )
855            {
856                if ( sourceSection.getName().equals( t.getName() ) )
857                {
858                    targetSection = t;
859                    break;
860                }
861            }
862
863            if ( targetSection != null )
864            {
865                if ( !preserveExisting )
866                {
867                    targetSection.setName( sourceSection.getName() );
868                    targetSection.setHeadTemplate( sourceSection.getHeadTemplate() );
869                    targetSection.setTailTemplate( sourceSection.getTailTemplate() );
870
871                    if ( isFieldSet( sourceSection, "editable" ) )
872                    {
873                        targetSection.setEditable( sourceSection.isEditable() );
874                    }
875                    if ( isFieldSet( sourceSection, "indentationLevel" ) )
876                    {
877                        targetSection.setIndentationLevel( sourceSection.getIndentationLevel() );
878                    }
879                    if ( isFieldSet( sourceSection, "modelVersion" ) )
880                    {
881                        targetSection.setModelVersion( sourceSection.getModelVersion() );
882                    }
883                    if ( isFieldSet( sourceSection, "optional" ) )
884                    {
885                        targetSection.setOptional( sourceSection.isOptional() );
886                    }
887                }
888            }
889            else
890            {
891                targetSection = sourceSection.clone();
892                targetSourceSections.getSourceSection().add( targetSection );
893            }
894
895            if ( sourceSection.getSourceSections() != null )
896            {
897                if ( targetSection.getSourceSections() == null )
898                {
899                    targetSection.setSourceSections( new SourceSectionsType() );
900                }
901
902                this.overwriteSourceSections( targetSection.getSourceSections(), sourceSection.getSourceSections(),
903                                              preserveExisting );
904            }
905        }
906    }
907
908    private static boolean isFieldSet( final Object object, final String fieldName ) throws NoSuchFieldException
909    {
910        final Field field = getField( object.getClass(), fieldName );
911
912        if ( field == null )
913        {
914            throw new NoSuchFieldException( fieldName );
915        }
916
917        final boolean accessible = field.isAccessible();
918
919        try
920        {
921            field.setAccessible( true );
922            return field.get( object ) != null;
923        }
924        catch ( final IllegalAccessException e )
925        {
926            throw new AssertionError( e );
927        }
928        finally
929        {
930            field.setAccessible( accessible );
931        }
932    }
933
934    private static Field getField( final Class<?> clazz, final String name )
935    {
936        if ( clazz != null )
937        {
938            try
939            {
940                return clazz.getDeclaredField( name );
941            }
942            catch ( final NoSuchFieldException e )
943            {
944                return getField( clazz.getSuperclass(), name );
945            }
946        }
947
948        return null;
949    }
950
951    private static String getMessage( final Throwable t )
952    {
953        return t != null
954               ? t.getMessage() != null && t.getMessage().trim().length() > 0
955                 ? t.getMessage()
956                 : getMessage( t.getCause() )
957               : null;
958
959    }
960
961    private static String getMessage( final String key, final Object... args )
962    {
963        return MessageFormat.format( ResourceBundle.getBundle(
964            ToolsModelProvider.class.getName().replace( '.', '/' ), Locale.getDefault() ).getString( key ), args );
965
966    }
967
968}