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 4352 2012-03-01 11:19:48Z 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 4352 2012-03-01 11:19:48Z 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 ( specification.isClassDeclaration() && sourceFileType == null )
348                            {
349                                final SourceFilesType defaultSourceFiles =
350                                    this.getDefaultSourceFilesType( tool, specification );
351    
352                                if ( sourceFilesType != null )
353                                {
354                                    this.overwriteSourceFiles( sourceFilesType, defaultSourceFiles, true );
355                                }
356                                else
357                                {
358                                    specification.getAny().add( new ObjectFactory().createSourceFiles(
359                                        this.getDefaultSourceFilesType( tool, specification ) ) );
360    
361                                }
362                            }
363                        }
364                    }
365    
366                    if ( modules.getImplementations() != null )
367                    {
368                        final Map<Implementation, SourceFilesType> userSourceFiles =
369                            new HashMap<Implementation, SourceFilesType>();
370    
371                        InheritanceModel imodel = new InheritanceModel( modules );
372    
373                        for ( int i = 0, s0 = modules.getImplementations().getImplementation().size(); i < s0; i++ )
374                        {
375                            final Implementation implementation = modules.getImplementations().getImplementation().get( i );
376                            final SourceFileType sourceFileType = implementation.getAnyObject( SourceFileType.class );
377                            final SourceFilesType sourceFilesType = implementation.getAnyObject( SourceFilesType.class );
378    
379                            if ( sourceFileType == null )
380                            {
381                                if ( sourceFilesType != null )
382                                {
383                                    userSourceFiles.put( implementation, sourceFilesType );
384                                }
385                                else if ( implementation.isClassDeclaration() )
386                                {
387                                    final SourceFilesType defaultSourceFiles =
388                                        this.getDefaultSourceFilesType( tool, implementation );
389    
390                                    boolean finalAncestor = false;
391    
392                                    final Set<InheritanceModel.Node<JAXBElement<?>>> sourceFilesNodes =
393                                        imodel.getJaxbElementNodes( implementation.getIdentifier(), SOURCE_FILES_QNAME );
394    
395                                    for ( final InheritanceModel.Node<JAXBElement<?>> sourceFilesNode : sourceFilesNodes )
396                                    {
397                                        if ( sourceFilesNode.getModelObject().getValue() instanceof SourceFilesType )
398                                        {
399                                            final SourceFilesType ancestorSourceFiles =
400                                                (SourceFilesType) sourceFilesNode.getModelObject().getValue();
401    
402                                            this.overwriteSourceFiles( defaultSourceFiles, ancestorSourceFiles, false );
403    
404                                            if ( ancestorSourceFiles.isFinal() )
405                                            {
406                                                finalAncestor = true;
407                                            }
408                                        }
409                                    }
410    
411                                    if ( !finalAncestor )
412                                    {
413                                        implementation.getAny().add(
414                                            new ObjectFactory().createSourceFiles( defaultSourceFiles ) );
415    
416                                    }
417                                }
418                            }
419                        }
420    
421                        for ( final Map.Entry<Implementation, SourceFilesType> e : userSourceFiles.entrySet() )
422                        {
423                            this.overwriteSourceFiles( e.getValue(), this.getDefaultSourceFilesType( tool, e.getKey() ),
424                                                       true );
425    
426                        }
427    
428                        imodel = new InheritanceModel( modules );
429    
430                        for ( int i = 0, s0 = modules.getImplementations().getImplementation().size(); i < s0; i++ )
431                        {
432                            final Implementation implementation = modules.getImplementations().getImplementation().get( i );
433                            final SourceFilesType sourceFilesType = implementation.getAnyObject( SourceFilesType.class );
434    
435                            if ( sourceFilesType != null && !userSourceFiles.containsKey( implementation ) )
436                            {
437                                boolean override = false;
438    
439                                final Set<InheritanceModel.Node<JAXBElement<?>>> sourceFilesNodes =
440                                    imodel.getJaxbElementNodes( implementation.getIdentifier(), SOURCE_FILES_QNAME );
441    
442                                for ( final InheritanceModel.Node<JAXBElement<?>> e : sourceFilesNodes )
443                                {
444                                    if ( !e.getOverriddenNodes().isEmpty() )
445                                    {
446                                        override = true;
447                                        break;
448                                    }
449                                }
450    
451                                if ( override )
452                                {
453                                    sourceFilesType.setOverride( override );
454                                }
455                            }
456                        }
457                    }
458    
459                    if ( classpathModule != null )
460                    {
461                        modules.getModule().remove( classpathModule );
462                    }
463                }
464            }
465            else if ( context.isLoggable( Level.FINER ) )
466            {
467                context.log( Level.FINER, getMessage( "disabled", this.getClass().getSimpleName(),
468                                                      model.getIdentifier() ), null );
469    
470            }
471    
472            return provided;
473        }
474    
475        /**
476         * Creates a new default source files model for a given specification.
477         *
478         * @param tool The tool to use for generating type names.
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 tool} or {@code specification} is {@code null}.
484         */
485        private SourceFilesType getDefaultSourceFilesType( final JomcTool tool, final Specification specification )
486        {
487            if ( tool == null )
488            {
489                throw new NullPointerException( "tool" );
490            }
491            if ( specification == null )
492            {
493                throw new NullPointerException( "specification" );
494            }
495    
496            final SourceFilesType sourceFilesType = new SourceFilesType();
497            final SourceFileType sourceFileType = new SourceFileType();
498            sourceFilesType.getSourceFile().add( sourceFileType );
499    
500            sourceFileType.setIdentifier( "Default" );
501    
502            if ( specification.getClazz() != null )
503            {
504                sourceFileType.setLocation( new StringBuilder( specification.getClazz().length() + 5 ).append(
505                    specification.getClazz().replace( '.', '/' ) ).append( ".java" ).toString() );
506    
507            }
508    
509            sourceFileType.setTemplate( SPECIFICATION_TEMPLATE );
510            sourceFileType.setHeadComment( "//" );
511            sourceFileType.setSourceSections( new SourceSectionsType() );
512    
513            SourceSectionType s = new SourceSectionType();
514            s.setName( LICENSE_SECTION_NAME );
515            s.setHeadTemplate( SPECIFICATION_LICENSE_TEMPLATE );
516            s.setOptional( true );
517            sourceFileType.getSourceSections().getSourceSection().add( s );
518    
519            s = new SourceSectionType();
520            s.setName( ANNOTATIONS_SECTION_NAME );
521            s.setHeadTemplate( SPECIFICATION_ANNOTATIONS_TEMPLATE );
522            sourceFileType.getSourceSections().getSourceSection().add( s );
523    
524            s = new SourceSectionType();
525            s.setName( DOCUMENTATION_SECTION_NAME );
526            s.setHeadTemplate( SPECIFICATION_DOCUMENTATION_TEMPLATE );
527            s.setOptional( true );
528            sourceFileType.getSourceSections().getSourceSection().add( s );
529    
530            final String javaTypeName = tool.getJavaTypeName( specification, false );
531            if ( javaTypeName != null )
532            {
533                s = new SourceSectionType();
534                s.setName( javaTypeName );
535                s.setIndentationLevel( 1 );
536                s.setEditable( true );
537                sourceFileType.getSourceSections().getSourceSection().add( s );
538            }
539    
540            return sourceFilesType;
541        }
542    
543        /**
544         * Creates a new default source files model for a given implementation.
545         *
546         * @param tool The tool to use for generating type names.
547         * @param implementation The implementation to create a new default source files model for.
548         *
549         * @return A new default source files model for {@code implementation}.
550         *
551         * @throws NullPointerExeption if {@code tool} or {@code implementation} is {@code null}.
552         */
553        private SourceFilesType getDefaultSourceFilesType( final JomcTool tool, final Implementation implementation )
554        {
555            if ( tool == null )
556            {
557                throw new NullPointerException( "tool" );
558            }
559            if ( implementation == null )
560            {
561                throw new NullPointerException( "implementation" );
562            }
563    
564            final SourceFilesType sourceFilesType = new SourceFilesType();
565            final SourceFileType sourceFileType = new SourceFileType();
566            sourceFilesType.getSourceFile().add( sourceFileType );
567    
568            final Modules modules = ModelHelper.getModules( tool.getModel() );
569            Specifications specifications = null;
570            Dependencies dependencies = null;
571            Messages messages = null;
572            Properties properties = null;
573    
574            if ( modules != null )
575            {
576                specifications = modules.getSpecifications( implementation.getIdentifier() );
577                dependencies = modules.getDependencies( implementation.getIdentifier() );
578                messages = modules.getMessages( implementation.getIdentifier() );
579                properties = modules.getProperties( implementation.getIdentifier() );
580            }
581    
582            sourceFileType.setIdentifier( "Default" );
583    
584            if ( implementation.getClazz() != null )
585            {
586                sourceFileType.setLocation( new StringBuilder( implementation.getClazz().length() + 5 ).append(
587                    implementation.getClazz().replace( '.', '/' ) ).append( ".java" ).toString() );
588    
589            }
590    
591            sourceFileType.setTemplate( IMPLEMENTATION_TEMPLATE );
592            sourceFileType.setHeadComment( "//" );
593            sourceFileType.setSourceSections( new SourceSectionsType() );
594    
595            SourceSectionType s = new SourceSectionType();
596            s.setName( LICENSE_SECTION_NAME );
597            s.setHeadTemplate( IMPLEMENTATION_LICENSE_TEMPLATE );
598            s.setOptional( true );
599            sourceFileType.getSourceSections().getSourceSection().add( s );
600    
601            s = new SourceSectionType();
602            s.setName( ANNOTATIONS_SECTION_NAME );
603            s.setHeadTemplate( IMPLEMENTATION_ANNOTATIONS_TEMPLATE );
604            sourceFileType.getSourceSections().getSourceSection().add( s );
605    
606            s = new SourceSectionType();
607            s.setName( DOCUMENTATION_SECTION_NAME );
608            s.setHeadTemplate( IMPLEMENTATION_DOCUMENTATION_TEMPLATE );
609            s.setOptional( true );
610            sourceFileType.getSourceSections().getSourceSection().add( s );
611    
612            final List<String> implementedJavaTypeNames = tool.getImplementedJavaTypeNames( implementation, false );
613            for ( int i = 0, s0 = implementedJavaTypeNames.size(); i < s0; i++ )
614            {
615                s = new SourceSectionType();
616                s.setName( implementedJavaTypeNames.get( i ) );
617                s.setIndentationLevel( 1 );
618                s.setEditable( true );
619                sourceFileType.getSourceSections().getSourceSection().add( s );
620            }
621    
622            final String javaTypeName = tool.getJavaTypeName( implementation, false );
623            if ( javaTypeName != null && !implementedJavaTypeNames.contains( javaTypeName ) )
624            {
625                s = new SourceSectionType();
626                s.setName( javaTypeName );
627                s.setIndentationLevel( 1 );
628                s.setEditable( true );
629                sourceFileType.getSourceSections().getSourceSection().add( s );
630            }
631    
632            s = new SourceSectionType();
633            s.setName( CONSTRUCTORS_SECTION_NAME );
634            s.setIndentationLevel( 1 );
635            s.setHeadTemplate( CONSTRUCTORS_HEAD_TEMPLATE );
636            s.setTailTemplate( CONSTRUCTORS_TAIL_TEMPLATE );
637            s.setOptional( specifications == null || ( specifications.getSpecification().isEmpty()
638                                                       && specifications.getReference().isEmpty() ) );
639    
640            s.setSourceSections( new SourceSectionsType() );
641            sourceFileType.getSourceSections().getSourceSection().add( s );
642    
643            final SourceSectionType defaultCtor = new SourceSectionType();
644            defaultCtor.setName( DEFAULT_CONSTRUCTOR_SECTION_NAME );
645            defaultCtor.setIndentationLevel( 2 );
646            defaultCtor.setHeadTemplate( DEFAULT_CONSTRUCTOR_TEMPLATE );
647            defaultCtor.setEditable( true );
648            s.getSourceSections().getSourceSection().add( defaultCtor );
649    
650            s = new SourceSectionType();
651            s.setName( DEPENDENCIES_SECTION_NAME );
652            s.setIndentationLevel( 1 );
653            s.setHeadTemplate( DEPENDENCIES_TEMPLATE );
654            s.setOptional( dependencies == null || dependencies.getDependency().isEmpty() );
655            sourceFileType.getSourceSections().getSourceSection().add( s );
656    
657            s = new SourceSectionType();
658            s.setName( PROPERTIES_SECTION_NAME );
659            s.setIndentationLevel( 1 );
660            s.setHeadTemplate( PROPERTIES_TEMPLATE );
661            s.setOptional( properties == null || properties.getProperty().isEmpty() );
662            sourceFileType.getSourceSections().getSourceSection().add( s );
663    
664            s = new SourceSectionType();
665            s.setName( MESSAGES_SECTION_NAME );
666            s.setIndentationLevel( 1 );
667            s.setHeadTemplate( MESSAGES_TEMPLATE );
668            s.setOptional( messages == null || messages.getMessage().isEmpty() );
669            sourceFileType.getSourceSections().getSourceSection().add( s );
670    
671            return sourceFilesType;
672        }
673    
674        /**
675         * Overwrites a list of source code files with another list of source code files.
676         *
677         * @param targetSourceFiles The list to overwrite.
678         * @param sourceSourceFiles The list to overwrite with.
679         * @param preserveExisting {@code true}, to preserve existing attributes of source code files and sections;
680         * {@code false}, to overwrite existing attributes of source code files and sections.
681         *
682         * @throws NullPointerException if {@code targetSourceFiles} or {@code sourceSourceFiles} is {@code null}.
683         */
684        private void overwriteSourceFiles( final SourceFilesType targetSourceFiles, final SourceFilesType sourceSourceFiles,
685                                           final boolean preserveExisting )
686        {
687            if ( targetSourceFiles == null )
688            {
689                throw new NullPointerException( "targetSourceFiles" );
690            }
691            if ( sourceSourceFiles == null )
692            {
693                throw new NullPointerException( "sourceSourceFiles" );
694            }
695    
696            try
697            {
698                for ( final SourceFileType s : sourceSourceFiles.getSourceFile() )
699                {
700                    final SourceFileType targetSourceFile = targetSourceFiles.getSourceFile( s.getIdentifier() );
701    
702                    if ( targetSourceFile != null )
703                    {
704                        this.overwriteSourceFile( targetSourceFile, s, preserveExisting );
705                    }
706                }
707            }
708            catch ( final NoSuchFieldException e )
709            {
710                throw new AssertionError( e );
711            }
712        }
713    
714        /**
715         * Overwrites a source code file with another source code file.
716         *
717         * @param targetSourceFile The source code file to overwrite.
718         * @param sourceSourceFile The source code file to overwrite with.
719         * @param preserveExisting {@code true}, to preserve existing attributes of the given source code file and sections;
720         * {@code false}, to overwrite existing attributes of the given source code file and sections.
721         *
722         * @throws NullPointerException if {@code targetSourceFile} or {@code sourceSourceFile} is {@code null}.
723         */
724        private void overwriteSourceFile( final SourceFileType targetSourceFile, final SourceFileType sourceSourceFile,
725                                          final boolean preserveExisting )
726            throws NoSuchFieldException
727        {
728            if ( targetSourceFile == null )
729            {
730                throw new NullPointerException( "targetSourceFile" );
731            }
732            if ( sourceSourceFile == null )
733            {
734                throw new NullPointerException( "sourceSourceFile" );
735            }
736    
737            if ( !preserveExisting )
738            {
739                targetSourceFile.setIdentifier( sourceSourceFile.getIdentifier() );
740                targetSourceFile.setLocation( sourceSourceFile.getLocation() );
741                targetSourceFile.setTemplate( sourceSourceFile.getTemplate() );
742                targetSourceFile.setHeadComment( sourceSourceFile.getHeadComment() );
743                targetSourceFile.setTailComment( sourceSourceFile.getTailComment() );
744    
745                if ( isFieldSet( sourceSourceFile, "_final" ) )
746                {
747                    targetSourceFile.setFinal( sourceSourceFile.isFinal() );
748                }
749                if ( isFieldSet( sourceSourceFile, "modelVersion" ) )
750                {
751                    targetSourceFile.setModelVersion( sourceSourceFile.getModelVersion() );
752                }
753                if ( isFieldSet( sourceSourceFile, "override" ) )
754                {
755                    targetSourceFile.setOverride( sourceSourceFile.isOverride() );
756                }
757            }
758    
759            if ( sourceSourceFile.getSourceSections() != null )
760            {
761                if ( targetSourceFile.getSourceSections() == null )
762                {
763                    targetSourceFile.setSourceSections( new SourceSectionsType() );
764                }
765    
766                this.overwriteSourceSections( targetSourceFile.getSourceSections(), sourceSourceFile.getSourceSections(),
767                                              preserveExisting );
768    
769            }
770        }
771    
772        /**
773         * Overwrites source code file sections with other source code file sections.
774         *
775         * @param targetSourceSections The source code file sections to overwrite.
776         * @param sourceSourceSections The source code file sections to overwrite with.
777         * @param preserveExisting {@code true}, to preserve existing attributes of the given source code file sections;
778         * {@code false}, to overwrite existing attributes of the given source code file sections.
779         *
780         * @throws NullPointerException if {@code targetSourceSections} or {@code sourceSourceSections} is {@code null}.
781         */
782        private void overwriteSourceSections( final SourceSectionsType targetSourceSections,
783                                              final SourceSectionsType sourceSourceSections,
784                                              final boolean preserveExisting ) throws NoSuchFieldException
785        {
786            if ( targetSourceSections == null )
787            {
788                throw new NullPointerException( "targetSourceSections" );
789            }
790            if ( sourceSourceSections == null )
791            {
792                throw new NullPointerException( "sourceSourceSections" );
793            }
794    
795            for ( final SourceSectionType sourceSection : sourceSourceSections.getSourceSection() )
796            {
797                SourceSectionType targetSection = null;
798    
799                for ( final SourceSectionType t : targetSourceSections.getSourceSection() )
800                {
801                    if ( sourceSection.getName().equals( t.getName() ) )
802                    {
803                        targetSection = t;
804                        break;
805                    }
806                }
807    
808                if ( targetSection != null )
809                {
810                    if ( !preserveExisting )
811                    {
812                        targetSection.setName( sourceSection.getName() );
813                        targetSection.setHeadTemplate( sourceSection.getHeadTemplate() );
814                        targetSection.setTailTemplate( sourceSection.getTailTemplate() );
815    
816                        if ( isFieldSet( sourceSection, "editable" ) )
817                        {
818                            targetSection.setEditable( sourceSection.isEditable() );
819                        }
820                        if ( isFieldSet( sourceSection, "indentationLevel" ) )
821                        {
822                            targetSection.setIndentationLevel( sourceSection.getIndentationLevel() );
823                        }
824                        if ( isFieldSet( sourceSection, "modelVersion" ) )
825                        {
826                            targetSection.setModelVersion( sourceSection.getModelVersion() );
827                        }
828                        if ( isFieldSet( sourceSection, "optional" ) )
829                        {
830                            targetSection.setOptional( sourceSection.isOptional() );
831                        }
832                    }
833                }
834                else
835                {
836                    targetSection = sourceSection.clone();
837                    targetSourceSections.getSourceSection().add( targetSection );
838                }
839    
840                if ( sourceSection.getSourceSections() != null )
841                {
842                    if ( targetSection.getSourceSections() == null )
843                    {
844                        targetSection.setSourceSections( new SourceSectionsType() );
845                    }
846    
847                    this.overwriteSourceSections( targetSection.getSourceSections(), sourceSection.getSourceSections(),
848                                                  preserveExisting );
849                }
850            }
851        }
852    
853        private static boolean isFieldSet( final Object object, final String fieldName ) throws NoSuchFieldException
854        {
855            final Field field = getField( object.getClass(), fieldName );
856    
857            if ( field == null )
858            {
859                throw new NoSuchFieldException( fieldName );
860            }
861    
862            final boolean accessible = field.isAccessible();
863    
864            try
865            {
866                field.setAccessible( true );
867                return field.get( object ) != null;
868            }
869            catch ( final IllegalAccessException e )
870            {
871                throw new AssertionError( e );
872            }
873            finally
874            {
875                field.setAccessible( accessible );
876            }
877        }
878    
879        private static Field getField( final Class<?> clazz, final String name )
880        {
881            if ( clazz != null )
882            {
883                try
884                {
885                    return clazz.getDeclaredField( name );
886                }
887                catch ( final NoSuchFieldException e )
888                {
889                    return getField( clazz.getSuperclass(), name );
890                }
891            }
892    
893            return null;
894        }
895    
896        private static String getMessage( final String key, final Object... args )
897        {
898            return MessageFormat.format( ResourceBundle.getBundle(
899                ToolsModelProvider.class.getName().replace( '.', '/' ), Locale.getDefault() ).getString( key ), args );
900    
901        }
902    
903    }