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