001    /*
002     *   Copyright (c) 2009 The JOMC Project
003     *   Copyright (c) 2005 Christian Schulte <schulte2005@users.sourceforge.net>
004     *   All rights reserved.
005     *
006     *   Redistribution and use in source and binary forms, with or without
007     *   modification, are permitted provided that the following conditions
008     *   are met:
009     *
010     *     o Redistributions of source code must retain the above copyright
011     *       notice, this list of conditions and the following disclaimer.
012     *
013     *     o Redistributions in binary form must reproduce the above copyright
014     *       notice, this list of conditions and the following disclaimer in
015     *       the documentation and/or other materials provided with the
016     *       distribution.
017     *
018     *   THIS SOFTWARE IS PROVIDED BY THE JOMC PROJECT AND CONTRIBUTORS "AS IS"
019     *   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020     *   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021     *   PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE JOMC PROJECT OR
022     *   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023     *   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024     *   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025     *   OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026     *   WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
027     *   OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
028     *   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029     *
030     *   $Id: SourceFileProcessor.java 2164 2010-06-28 03:25:51Z schulte2005 $
031     *
032     */
033    package org.jomc.tools;
034    
035    import java.io.File;
036    import java.io.IOException;
037    import java.io.StringWriter;
038    import java.text.MessageFormat;
039    import java.util.LinkedList;
040    import java.util.List;
041    import java.util.ResourceBundle;
042    import java.util.logging.Level;
043    import org.apache.commons.io.FileUtils;
044    import org.apache.velocity.Template;
045    import org.apache.velocity.VelocityContext;
046    import org.jomc.model.Dependencies;
047    import org.jomc.model.Implementation;
048    import org.jomc.model.Messages;
049    import org.jomc.model.Module;
050    import org.jomc.model.Properties;
051    import org.jomc.model.Specification;
052    import org.jomc.model.Specifications;
053    import org.jomc.tools.model.SourceFileType;
054    import org.jomc.tools.model.SourceFilesType;
055    import org.jomc.tools.model.SourceSectionType;
056    import org.jomc.tools.model.SourceSectionsType;
057    import org.jomc.util.LineEditor;
058    import org.jomc.util.Section;
059    import org.jomc.util.SectionEditor;
060    import org.jomc.util.TrailingWhitespaceEditor;
061    
062    /**
063     * Processes source code files.
064     *
065     * <p><b>Use cases</b><br/><ul>
066     * <li>{@link #manageSourceFiles(File) }</li>
067     * <li>{@link #manageSourceFiles(Module, File) }</li>
068     * <li>{@link #manageSourceFiles(Specification, File) }</li>
069     * <li>{@link #manageSourceFiles(Implementation, File) }</li>
070     * </ul></p>
071     *
072     * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a>
073     * @version $Id: SourceFileProcessor.java 2164 2010-06-28 03:25:51Z schulte2005 $
074     */
075    public class SourceFileProcessor extends JomcTool
076    {
077    
078        /** Constant for the name of the constructors source code section. */
079        private static final String CONSTRUCTORS_SECTION_NAME = "Constructors";
080    
081        /** Constant for the name of the default constructor source code section. */
082        private static final String DEFAULT_CONSTRUCTOR_SECTION_NAME = "Default Constructor";
083    
084        /** Constant for the name of the dependencies source code section. */
085        private static final String DEPENDENCIES_SECTION_NAME = "Dependencies";
086    
087        /** Constant for the name of the properties source code section. */
088        private static final String PROPERTIES_SECTION_NAME = "Properties";
089    
090        /** Constant for the name of the messages source code section. */
091        private static final String MESSAGES_SECTION_NAME = "Messages";
092    
093        /** Constant for the name of the license source code section. */
094        private static final String LICENSE_SECTION_NAME = "License Header";
095    
096        /** Constant for the name of the documentation source code section. */
097        private static final String DOCUMENTATION_SECTION_NAME = "Documentation";
098    
099        /** Constant for the name of the implementation annotations source code section. */
100        private static final String ANNOTATIONS_SECTION_NAME = "Annotations";
101    
102        /** Name of the {@code implementation-constructors-head.vm} template. */
103        private static final String CONSTRUCTORS_HEAD_TEMPLATE = "implementation-constructors-head.vm";
104    
105        /** Name of the {@code implementation-constructors-tail.vm} template. */
106        private static final String CONSTRUCTORS_TAIL_TEMPLATE = "implementation-constructors-tail.vm";
107    
108        /** Name of the {@code implementation-default-constructor.vm} template. */
109        private static final String DEFAULT_CONSTRUCTOR_TEMPLATE = "implementation-default-constructor.vm";
110    
111        /** Name of the {@code implementation-dependencies.vm} template. */
112        private static final String DEPENDENCIES_TEMPLATE = "implementation-dependencies.vm";
113    
114        /** Name of the {@code implementation-properties.vm} template. */
115        private static final String PROPERTIES_TEMPLATE = "implementation-properties.vm";
116    
117        /** Name of the {@code implementation-messages.vm} template. */
118        private static final String MESSAGES_TEMPLATE = "implementation-messages.vm";
119    
120        /** Name of the {@code specification-license.vm} template. */
121        private static final String SPECIFICATION_LICENSE_TEMPLATE = "specification-license.vm";
122    
123        /** Name of the {@code implementation-license.vm} template. */
124        private static final String IMPLEMENTATION_LICENSE_TEMPLATE = "implementation-license.vm";
125    
126        /** Name of the {@code specification-documentation.vm} template. */
127        private static final String SPECIFICATION_DOCUMENTATION_TEMPLATE = "specification-documentation.vm";
128    
129        /** Name of the {@code implementation-documentation.vm} template. */
130        private static final String IMPLEMENTATION_DOCUMENTATION_TEMPLATE = "implementation-documentation.vm";
131    
132        /** Name of the {@code Implementation.java.vm} template. */
133        private static final String IMPLEMENTATION_TEMPLATE = "Implementation.java.vm";
134    
135        /** Name of the {@code Specification.java.vm} template. */
136        private static final String SPECIFICATION_TEMPLATE = "Specification.java.vm";
137    
138        /** Name of the {@code specification-annotations.vm} template. */
139        private static final String SPECIFICATION_ANNOTATIONS_TEMPLATE = "specification-annotations.vm";
140    
141        /** Name of the {@code implementation-annotations.vm} template. */
142        private static final String IMPLEMENTATION_ANNOTATIONS_TEMPLATE = "implementation-annotations.vm";
143    
144        /** Source files model. */
145        private SourceFilesType sourceFilesType;
146    
147        /** Creates a new {@code SourceFileProcessor} instance. */
148        public SourceFileProcessor()
149        {
150            super();
151        }
152    
153        /**
154         * Creates a new {@code SourceFileProcessor} instance taking a {@code SourceFileProcessor} instance to initialize
155         * the instance with.
156         *
157         * @param tool The instance to initialize the new instance with,
158         *
159         * @throws NullPointerException if {@code tool} is {@code null}.
160         * @throws IOException if copying {@code tool} fails.
161         */
162        public SourceFileProcessor( final SourceFileProcessor tool ) throws IOException
163        {
164            super( tool );
165            this.sourceFilesType = tool.sourceFilesType != null ? new SourceFilesType( tool.sourceFilesType ) : null;
166        }
167    
168        /**
169         * Gets the source files model of the instance.
170         * <p>This accessor method returns a reference to the live object, not a snapshot. Therefore any modification you
171         * make to the returned object will be present inside the object. This is why there is no {@code set} method.</p>
172         *
173         * @return The source files model of the instance.
174         *
175         * @see #getSourceFileType(org.jomc.model.Specification)
176         * @see #getSourceFileType(org.jomc.model.Implementation)
177         */
178        public SourceFilesType getSourceFilesType()
179        {
180            if ( this.sourceFilesType == null )
181            {
182                this.sourceFilesType = new SourceFilesType();
183            }
184    
185            return this.sourceFilesType;
186        }
187    
188        /**
189         * Gets the model of a specification source file of the modules of the instance.
190         *
191         * @param specification The specification to get a source file model for.
192         *
193         * @return The source file model for {@code specification}.
194         *
195         * @throws NullPointerException if {@code specification} is {@code null}.
196         *
197         * @see #getSourceFilesType()
198         */
199        public SourceFileType getSourceFileType( final Specification specification )
200        {
201            if ( specification == null )
202            {
203                throw new NullPointerException( "specification" );
204            }
205    
206            assert this.getModules().getSpecification( specification.getIdentifier() ) != null :
207                "Specification '" + specification.getIdentifier() + "' not found.";
208    
209            SourceFileType sourceFileType = this.getSourceFilesType().getSourceFile( specification.getIdentifier() );
210    
211            if ( sourceFileType == null )
212            {
213                sourceFileType = specification.getAnyObject( SourceFileType.class );
214            }
215    
216            if ( sourceFileType == null )
217            {
218                sourceFileType = new SourceFileType();
219                sourceFileType.setIdentifier( specification.getIdentifier() );
220                sourceFileType.setLocation( specification.getClazz().replace( '.', '/' ) + ".java" );
221                sourceFileType.setTemplate( SPECIFICATION_TEMPLATE );
222                sourceFileType.setSourceSections( new SourceSectionsType() );
223    
224                SourceSectionType s = new SourceSectionType();
225                s.setName( LICENSE_SECTION_NAME );
226                s.setHeadTemplate( SPECIFICATION_LICENSE_TEMPLATE );
227                s.setOptional( true );
228                sourceFileType.getSourceSections().getSourceSection().add( s );
229    
230                s = new SourceSectionType();
231                s.setName( ANNOTATIONS_SECTION_NAME );
232                s.setHeadTemplate( SPECIFICATION_ANNOTATIONS_TEMPLATE );
233                s.setOptional( false );
234                sourceFileType.getSourceSections().getSourceSection().add( s );
235    
236                s = new SourceSectionType();
237                s.setName( DOCUMENTATION_SECTION_NAME );
238                s.setHeadTemplate( SPECIFICATION_DOCUMENTATION_TEMPLATE );
239                s.setOptional( true );
240                sourceFileType.getSourceSections().getSourceSection().add( s );
241    
242                final String javaTypeName = this.getJavaTypeName( specification, false );
243                if ( javaTypeName != null )
244                {
245                    s = new SourceSectionType();
246                    s.setName( javaTypeName );
247                    s.setIndentationLevel( 1 );
248                    s.setOptional( false );
249                    s.setEditable( true );
250                    sourceFileType.getSourceSections().getSourceSection().add( s );
251                }
252            }
253    
254            return sourceFileType;
255        }
256    
257        /**
258         * Gets the model of an implementation source file of the modules of the instance.
259         *
260         * @param implementation The implementation to get a source file model for.
261         *
262         * @return The source file model for {@code implementation}.
263         *
264         * @throws NullPointerException if {@code implementation} is {@code null}.
265         *
266         * @see #getSourceFilesType()
267         */
268        public SourceFileType getSourceFileType( final Implementation implementation )
269        {
270            if ( implementation == null )
271            {
272                throw new NullPointerException( "implementation" );
273            }
274    
275            assert this.getModules().getImplementation( implementation.getIdentifier() ) != null :
276                "Implementation '" + implementation.getIdentifier() + "' not found.";
277    
278            SourceFileType sourceFileType = this.getSourceFilesType().getSourceFile( implementation.getIdentifier() );
279    
280            if ( sourceFileType == null )
281            {
282                sourceFileType = implementation.getAnyObject( SourceFileType.class );
283            }
284    
285            if ( sourceFileType == null )
286            {
287                final Specifications specifications = this.getModules().getSpecifications( implementation.getIdentifier() );
288                final Dependencies dependencies = this.getModules().getDependencies( implementation.getIdentifier() );
289                final Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
290                final Properties properties = this.getModules().getProperties( implementation.getIdentifier() );
291    
292                sourceFileType = new SourceFileType();
293                sourceFileType.setIdentifier( implementation.getIdentifier() );
294                sourceFileType.setLocation( implementation.getClazz().replace( '.', '/' ) + ".java" );
295                sourceFileType.setTemplate( IMPLEMENTATION_TEMPLATE );
296                sourceFileType.setSourceSections( new SourceSectionsType() );
297    
298                SourceSectionType s = new SourceSectionType();
299                s.setName( LICENSE_SECTION_NAME );
300                s.setHeadTemplate( IMPLEMENTATION_LICENSE_TEMPLATE );
301                s.setOptional( true );
302                sourceFileType.getSourceSections().getSourceSection().add( s );
303    
304                s = new SourceSectionType();
305                s.setName( ANNOTATIONS_SECTION_NAME );
306                s.setHeadTemplate( IMPLEMENTATION_ANNOTATIONS_TEMPLATE );
307                s.setOptional( false );
308                sourceFileType.getSourceSections().getSourceSection().add( s );
309    
310                s = new SourceSectionType();
311                s.setName( DOCUMENTATION_SECTION_NAME );
312                s.setHeadTemplate( IMPLEMENTATION_DOCUMENTATION_TEMPLATE );
313                s.setOptional( true );
314                sourceFileType.getSourceSections().getSourceSection().add( s );
315    
316                for ( String interfaceName : this.getJavaInterfaceNames( implementation, false ) )
317                {
318                    s = new SourceSectionType();
319                    s.setName( interfaceName );
320                    s.setIndentationLevel( 1 );
321                    s.setOptional( false );
322                    s.setEditable( true );
323                    sourceFileType.getSourceSections().getSourceSection().add( s );
324                }
325    
326                s = new SourceSectionType();
327                s.setName( this.getJavaTypeName( implementation, false ) );
328                s.setIndentationLevel( 1 );
329                s.setOptional( false );
330                s.setEditable( true );
331                sourceFileType.getSourceSections().getSourceSection().add( s );
332    
333                s = new SourceSectionType();
334                s.setName( CONSTRUCTORS_SECTION_NAME );
335                s.setIndentationLevel( 1 );
336                s.setHeadTemplate( CONSTRUCTORS_HEAD_TEMPLATE );
337                s.setTailTemplate( CONSTRUCTORS_TAIL_TEMPLATE );
338                s.setOptional( specifications == null || ( specifications.getSpecification().isEmpty()
339                                                           && specifications.getReference().isEmpty() ) );
340    
341                s.setSourceSections( new SourceSectionsType() );
342                sourceFileType.getSourceSections().getSourceSection().add( s );
343    
344                final SourceSectionType defaultCtor = new SourceSectionType();
345                defaultCtor.setName( DEFAULT_CONSTRUCTOR_SECTION_NAME );
346                defaultCtor.setIndentationLevel( 2 );
347                defaultCtor.setHeadTemplate( DEFAULT_CONSTRUCTOR_TEMPLATE );
348                defaultCtor.setOptional( false );
349                defaultCtor.setEditable( true );
350                s.getSourceSections().getSourceSection().add( defaultCtor );
351    
352                s = new SourceSectionType();
353                s.setName( DEPENDENCIES_SECTION_NAME );
354                s.setIndentationLevel( 1 );
355                s.setHeadTemplate( DEPENDENCIES_TEMPLATE );
356                s.setOptional( dependencies == null || dependencies.getDependency().isEmpty() );
357                sourceFileType.getSourceSections().getSourceSection().add( s );
358    
359                s = new SourceSectionType();
360                s.setName( PROPERTIES_SECTION_NAME );
361                s.setIndentationLevel( 1 );
362                s.setHeadTemplate( PROPERTIES_TEMPLATE );
363                s.setOptional( properties == null || properties.getProperty().isEmpty() );
364                sourceFileType.getSourceSections().getSourceSection().add( s );
365    
366                s = new SourceSectionType();
367                s.setName( MESSAGES_SECTION_NAME );
368                s.setIndentationLevel( 1 );
369                s.setHeadTemplate( MESSAGES_TEMPLATE );
370                s.setOptional( messages == null || messages.getMessage().isEmpty() );
371                sourceFileType.getSourceSections().getSourceSection().add( s );
372            }
373    
374            return sourceFileType;
375        }
376    
377        /**
378         * Manages the source files of the modules of the instance.
379         *
380         * @param sourcesDirectory The directory holding the source files to manage.
381         *
382         * @throws NullPointerException if {@code sourcesDirectory} is {@code null}.
383         * @throws IOException if managing source files fails.
384         *
385         * @see #manageSourceFiles(org.jomc.model.Module, java.io.File)
386         */
387        public void manageSourceFiles( final File sourcesDirectory ) throws IOException
388        {
389            if ( sourcesDirectory == null )
390            {
391                throw new NullPointerException( "sourcesDirectory" );
392            }
393    
394            for ( Module m : this.getModules().getModule() )
395            {
396                this.manageSourceFiles( m, sourcesDirectory );
397            }
398        }
399    
400        /**
401         * Manages the source files of a given module of the modules of the instance.
402         *
403         * @param module The module to process.
404         * @param sourcesDirectory The directory holding the source files to manage.
405         *
406         * @throws NullPointerException if {@code module} or {@code sourcesDirectory} is {@code null}.
407         * @throws IOException if managing source files fails.
408         *
409         * @see #manageSourceFiles(org.jomc.model.Specification, java.io.File)
410         * @see #manageSourceFiles(org.jomc.model.Implementation, java.io.File)
411         */
412        public void manageSourceFiles( final Module module, final File sourcesDirectory ) throws IOException
413        {
414            if ( module == null )
415            {
416                throw new NullPointerException( "module" );
417            }
418            if ( sourcesDirectory == null )
419            {
420                throw new NullPointerException( "sourcesDirectory" );
421            }
422    
423            assert this.getModules().getModule( module.getName() ) != null : "Module '" + module.getName() + "' not found.";
424    
425            if ( module.getSpecifications() != null )
426            {
427                for ( Specification s : module.getSpecifications().getSpecification() )
428                {
429                    this.manageSourceFiles( s, sourcesDirectory );
430                }
431            }
432            if ( module.getImplementations() != null )
433            {
434                for ( Implementation i : module.getImplementations().getImplementation() )
435                {
436                    this.manageSourceFiles( i, sourcesDirectory );
437                }
438            }
439        }
440    
441        /**
442         * Manages the source file of a given specification of the modules of the instance.
443         *
444         * @param specification The specification to process.
445         * @param sourcesDirectory The directory holding the source files to manage.
446         *
447         * @throws NullPointerException if {@code specification} or {@code sourcesDirectory} is {@code null}.
448         * @throws IOException if managing source files fails.
449         *
450         * @see #getSourceFileEditor(org.jomc.model.Specification)
451         */
452        public void manageSourceFiles( final Specification specification, final File sourcesDirectory ) throws IOException
453        {
454            if ( specification == null )
455            {
456                throw new NullPointerException( "specification" );
457            }
458            if ( sourcesDirectory == null )
459            {
460                throw new NullPointerException( "sourcesDirectory" );
461            }
462    
463            assert this.getModules().getSpecification( specification.getIdentifier() ) != null :
464                "Specification '" + specification.getIdentifier() + "' not found.";
465    
466            final Implementation i = this.getModules().getImplementation( specification.getIdentifier() );
467    
468            if ( i != null && i.isClassDeclaration() )
469            {
470                this.manageSourceFiles( i, sourcesDirectory );
471            }
472            else if ( specification.isClassDeclaration() )
473            {
474                this.editSourceFile( sourcesDirectory, this.getSourceFileEditor( specification ) );
475            }
476        }
477    
478        /**
479         * Manages the source file of a given implementation of the modules of the instance.
480         *
481         * @param implementation The implementation to process.
482         * @param sourcesDirectory The directory holding the source files to manage.
483         *
484         * @throws NullPointerException if {@code implementation} or {@code sourcesDirectory} is {@code null}.
485         * @throws IOException if managing source files fails.
486         *
487         * @see #getSourceFileEditor(org.jomc.model.Implementation)
488         */
489        public void manageSourceFiles( final Implementation implementation, final File sourcesDirectory )
490            throws IOException
491        {
492            if ( implementation == null )
493            {
494                throw new NullPointerException( "implementation" );
495            }
496            if ( sourcesDirectory == null )
497            {
498                throw new NullPointerException( "sourcesDirectory" );
499            }
500    
501            assert this.getModules().getImplementation( implementation.getIdentifier() ) != null :
502                "Implementation '" + implementation.getIdentifier() + "' not found.";
503    
504            if ( implementation.isClassDeclaration() )
505            {
506                this.editSourceFile( sourcesDirectory, this.getSourceFileEditor( implementation ) );
507            }
508        }
509    
510        /**
511         * Gets a new editor for editing the source file of a given specification of the modules of the instance.
512         *
513         * @param specification The specification whose source file to edit.
514         *
515         * @return A new editor for editing the source file of {@code specification}.
516         *
517         * @throws NullPointerException if {@code specification} is {@code null}.
518         */
519        public SourceFileEditor getSourceFileEditor( final Specification specification )
520        {
521            if ( specification == null )
522            {
523                throw new NullPointerException( "specification" );
524            }
525    
526            assert this.getModules().getSpecification( specification.getIdentifier() ) != null :
527                "Specification '" + specification.getIdentifier() + "' not found.";
528    
529            return new SourceFileEditor( specification, new TrailingWhitespaceEditor( this.getLineSeparator() ),
530                                         this.getLineSeparator() );
531    
532        }
533    
534        /**
535         * Gets a new editor for editing the source file of a given implementation of the modules of the instance.
536         *
537         * @param implementation The implementation whose source file to edit.
538         *
539         * @return A new editor for editing the source file of {@code implementation}.
540         *
541         * @throws NullPointerException if {@code implementation} is {@code null}.
542         */
543        public SourceFileEditor getSourceFileEditor( final Implementation implementation )
544        {
545            if ( implementation == null )
546            {
547                throw new NullPointerException( "implementation" );
548            }
549    
550            assert this.getModules().getImplementation( implementation.getIdentifier() ) != null :
551                "Implementation '" + implementation.getIdentifier() + "' not found.";
552    
553            return new SourceFileEditor( implementation, new TrailingWhitespaceEditor( this.getLineSeparator() ),
554                                         this.getLineSeparator() );
555    
556        }
557    
558        /**
559         * Edits a given file using a given editor.
560         *
561         * @param sourcesDirectory The directory holding the source file to edit.
562         * @param editor The editor to edit {@code f} with.
563         *
564         * @throws NullPointerException if {@code sourcesDirectory} or {@code editor} is {@code null}.
565         * @throws IOException if editing fails.
566         */
567        private void editSourceFile( final File sourcesDirectory, final SourceFileEditor editor ) throws IOException
568        {
569            if ( sourcesDirectory == null )
570            {
571                throw new NullPointerException( "sourcesDirectory" );
572            }
573            if ( editor == null )
574            {
575                throw new NullPointerException( "editor" );
576            }
577    
578            final SourceFileType sourceFileType = editor.getSourceFileType();
579            if ( sourceFileType != null )
580            {
581                String content = "";
582                String edited = null;
583                final File f = new File( sourcesDirectory, sourceFileType.getLocation() );
584    
585                if ( !f.exists() )
586                {
587                    if ( sourceFileType.getTemplate() != null )
588                    {
589                        if ( this.isLoggable( Level.FINE ) )
590                        {
591                            this.log( Level.FINE, getMessage( "creating", this.getClass().getName(),
592                                                              f.getAbsolutePath() ), null );
593    
594                        }
595    
596                        final StringWriter writer = new StringWriter();
597                        final Template template = this.getVelocityTemplate( sourceFileType.getTemplate() );
598                        final VelocityContext ctx = editor.getVelocityContext();
599                        ctx.put( "template", template );
600                        template.merge( ctx, writer );
601                        writer.close();
602                        content = writer.toString();
603                    }
604                }
605                else
606                {
607                    if ( this.isLoggable( Level.FINE ) )
608                    {
609                        this.log( Level.FINE, getMessage( "reading", this.getClass().getName(),
610                                                          f.getAbsolutePath() ), null );
611    
612                    }
613    
614                    content = FileUtils.readFileToString( f, this.getInputEncoding() );
615                }
616    
617                try
618                {
619                    edited = editor.edit( content );
620                }
621                catch ( final IOException e )
622                {
623                    throw (IOException) new IOException( getMessage(
624                        "failedEditing", f.getAbsolutePath(), e.getMessage() ) ).initCause( e );
625    
626                }
627    
628                if ( this.isLoggable( Level.FINE ) )
629                {
630                    for ( Section s : editor.getAddedSections() )
631                    {
632                        this.log( Level.FINE, getMessage( "addedSection", this.getClass().getName(), f.getAbsolutePath(),
633                                                          s.getName() ), null );
634    
635                    }
636                }
637    
638                if ( this.isLoggable( Level.WARNING ) )
639                {
640                    for ( Section s : editor.getUnknownSections() )
641                    {
642                        this.log( Level.WARNING, getMessage( "unknownSection", f.getAbsolutePath(), s.getName() ), null );
643                    }
644                }
645    
646                if ( !edited.equals( content ) || edited.length() == 0 )
647                {
648                    if ( !f.getParentFile().exists() && !f.getParentFile().mkdirs() )
649                    {
650                        throw new IOException( getMessage(
651                            "failedCreatingDirectory", f.getParentFile().getAbsolutePath() ) );
652    
653                    }
654    
655                    if ( this.isLoggable( Level.INFO ) )
656                    {
657                        this.log( Level.INFO, getMessage( "editing", f.getAbsolutePath() ), null );
658                    }
659    
660                    FileUtils.writeStringToFile( f, edited, this.getOutputEncoding() );
661                }
662                else if ( this.isLoggable( Level.FINE ) )
663                {
664                    this.log( Level.FINE, getMessage( "unchanged", this.getClass().getName(), f.getAbsolutePath() ), null );
665                }
666            }
667        }
668    
669        private static String getMessage( final String key, final Object... arguments )
670        {
671            if ( key == null )
672            {
673                throw new NullPointerException( "key" );
674            }
675    
676            return MessageFormat.format( ResourceBundle.getBundle(
677                SourceFileProcessor.class.getName().replace( '.', '/' ) ).getString( key ), arguments );
678    
679        }
680    
681        /**
682         * Extension to {@code SectionEditor} adding support for editing source code files.
683         *
684         * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a>
685         * @version $Id: SourceFileProcessor.java 2164 2010-06-28 03:25:51Z schulte2005 $
686         */
687        public class SourceFileEditor extends SectionEditor
688        {
689    
690            /** {@code Specification} of the instance or {@code null}. */
691            private final Specification specification;
692    
693            /** {@code Implementation} of the instance or {@code null}. */
694            private final Implementation implementation;
695    
696            /** List of sections added to the input. */
697            private List<Section> addedSections;
698    
699            /** List of sections without corresponding model entry. */
700            private List<Section> unknownSections;
701    
702            /**
703             * Creates a new {@code SourceFileEditor} taking a {@code Specification} to edit source code of.
704             *
705             * @param specification The specification to edit source code of.
706             */
707            public SourceFileEditor( final Specification specification )
708            {
709                this( specification, null, null );
710            }
711    
712            /**
713             * Creates a new {@code SourceFileEditor} taking a {@code Specification} to edit source code of and a line
714             * separator.
715             *
716             * @param specification The specification to edit source code of.
717             * @param lineSeparator The line separator of the editor.
718             */
719            public SourceFileEditor( final Specification specification, final String lineSeparator )
720            {
721                this( specification, null, lineSeparator );
722            }
723    
724            /**
725             * Creates a new {@code SourceFileEditor} taking a {@code Specification} to edit source code of and an editor to
726             * chain.
727             *
728             * @param specification The specification backing the editor.
729             * @param lineEditor The editor to chain.
730             */
731            public SourceFileEditor( final Specification specification, final LineEditor lineEditor )
732            {
733                this( specification, lineEditor, null );
734            }
735    
736            /**
737             * Creates a new {@code SourceFileEditor} taking a {@code Specification} to edit source code of, an editor to
738             * chain and a line separator.
739             *
740             * @param specification The specification backing the editor.
741             * @param lineEditor The editor to chain.
742             * @param lineSeparator The line separator of the editor.
743             */
744            public SourceFileEditor( final Specification specification, final LineEditor lineEditor,
745                                     final String lineSeparator )
746            {
747                super( lineEditor, lineSeparator );
748                this.specification = specification;
749                this.implementation = null;
750    
751                assert getModules().getSpecification( specification.getIdentifier() ) != null :
752                    "Specification '" + specification.getIdentifier() + "' not found.";
753    
754            }
755    
756            /**
757             * Creates a new {@code SourceFileEditor} taking an {@code Implementation} to edit source code of.
758             *
759             * @param implementation The implementation to edit source code of.
760             */
761            public SourceFileEditor( final Implementation implementation )
762            {
763                this( implementation, null, null );
764            }
765    
766            /**
767             * Creates a new {@code SourceFileEditor} taking an {@code Implementation} to edit source code of and a line
768             * separator.
769             *
770             * @param implementation The implementation to edit source code of.
771             * @param lineSeparator The line separator of the editor.
772             */
773            public SourceFileEditor( final Implementation implementation, final String lineSeparator )
774            {
775                this( implementation, null, lineSeparator );
776            }
777    
778            /**
779             * Creates a new {@code SourceFileEditor} taking an {@code Implementation} to edit source code of and an editor
780             * to chain.
781             *
782             * @param implementation The implementation to edit source code of.
783             * @param lineEditor The editor to chain.
784             */
785            public SourceFileEditor( final Implementation implementation, final LineEditor lineEditor )
786            {
787                this( implementation, lineEditor, null );
788            }
789    
790            /**
791             * Creates a new {@code SourceFileEditor} taking an {@code Implementation} to edit source code of, an editor
792             * to chain and a line separator.
793             *
794             * @param implementation The implementation to edit source code of.
795             * @param lineEditor The editor to chain.
796             * @param lineSeparator The line separator of the editor.
797             */
798            public SourceFileEditor( final Implementation implementation, final LineEditor lineEditor,
799                                     final String lineSeparator )
800            {
801                super( lineEditor, lineSeparator );
802                this.implementation = implementation;
803                this.specification = null;
804    
805                assert getModules().getImplementation( implementation.getIdentifier() ) != null :
806                    "Implementation '" + implementation.getIdentifier() + "' not found.";
807    
808            }
809    
810            /**
811             * Gets a list of sections added to the input.
812             *
813             * @return A list of sections added to the input.
814             */
815            public List<Section> getAddedSections()
816            {
817                if ( this.addedSections == null )
818                {
819                    this.addedSections = new LinkedList<Section>();
820                }
821    
822                return this.addedSections;
823            }
824    
825            /**
826             * Gets a list of sections without corresponding model entry.
827             *
828             * @return A list of sections without corresponding model entry.
829             */
830            public List<Section> getUnknownSections()
831            {
832                if ( this.unknownSections == null )
833                {
834                    this.unknownSections = new LinkedList<Section>();
835                }
836    
837                return this.unknownSections;
838            }
839    
840            /**
841             * Gets the model of the editor.
842             *
843             * @return The model of the editor.
844             */
845            protected SourceFileType getSourceFileType()
846            {
847                if ( this.specification != null )
848                {
849                    return SourceFileProcessor.this.getSourceFileType( this.specification );
850                }
851    
852                if ( this.implementation != null )
853                {
854                    return SourceFileProcessor.this.getSourceFileType( this.implementation );
855                }
856    
857                return null;
858            }
859    
860            /**
861             * Gets the velocity context used for merging templates.
862             *
863             * @return The velocity context used for merging templates.
864             */
865            protected VelocityContext getVelocityContext()
866            {
867                final VelocityContext ctx = SourceFileProcessor.this.getVelocityContext();
868    
869                if ( this.specification != null )
870                {
871                    ctx.put( "specification", this.specification );
872                }
873    
874                if ( this.implementation != null )
875                {
876                    ctx.put( "implementation", this.implementation );
877                }
878    
879                return ctx;
880            }
881    
882            /**
883             * {@inheritDoc}
884             * <p>This method creates any sections declared in the model of the editor as returned by method
885             * {@code getSourceFileType} prior to rendering the output of the editor.</p>
886             *
887             * @param section The section to start rendering the editor's output with.
888             *
889             * @see #getSourceFileType()
890             */
891            @Override
892            protected String getOutput( final Section section ) throws IOException
893            {
894                this.getAddedSections().clear();
895                this.getUnknownSections().clear();
896    
897                final SourceFileType sourceFileType = this.getSourceFileType();
898    
899                if ( sourceFileType != null )
900                {
901                    this.createSections( sourceFileType.getSourceSections(), section );
902                }
903    
904                return super.getOutput( section );
905            }
906    
907            /**
908             * {@inheritDoc}
909             * <p>This method searches the model of the editor for a section matching {@code s} and updates properties
910             * {@code headContent} and {@code tailContent} of {@code s} according to the templates declared in the model
911             * as returned by method {@code getSourceFileType}.</p>
912             *
913             * @param s The section to edit.
914             *
915             * @see #getSourceFileType()
916             */
917            @Override
918            protected void editSection( final Section s ) throws IOException
919            {
920                super.editSection( s );
921    
922                final SourceFileType sourceFileType = this.getSourceFileType();
923    
924                if ( s.getName() != null && sourceFileType != null && sourceFileType.getSourceSections() != null )
925                {
926                    final SourceSectionType sourceSectionType =
927                        sourceFileType.getSourceSections().getSourceSection( s.getName() );
928    
929                    if ( sourceSectionType != null )
930                    {
931                        if ( s.getStartingLine() != null )
932                        {
933                            s.setStartingLine( getIndentation( sourceSectionType.getIndentationLevel() )
934                                               + s.getStartingLine().trim() );
935    
936                        }
937                        if ( s.getEndingLine() != null )
938                        {
939                            s.setEndingLine( getIndentation( sourceSectionType.getIndentationLevel() )
940                                             + s.getEndingLine().trim() );
941    
942                        }
943    
944                        if ( sourceSectionType.getHeadTemplate() != null
945                             && ( !sourceSectionType.isEditable() || s.getHeadContent().toString().trim().length() == 0 ) )
946                        {
947                            final StringWriter writer = new StringWriter();
948                            final Template template = getVelocityTemplate( sourceSectionType.getHeadTemplate() );
949                            final VelocityContext ctx = getVelocityContext();
950                            ctx.put( "template", template );
951                            template.merge( ctx, writer );
952                            writer.close();
953                            s.getHeadContent().setLength( 0 );
954                            s.getHeadContent().append( writer.toString() );
955                        }
956    
957                        if ( sourceSectionType.getTailTemplate() != null
958                             && ( !sourceSectionType.isEditable() || s.getTailContent().toString().trim().length() == 0 ) )
959                        {
960                            final StringWriter writer = new StringWriter();
961                            final Template template = getVelocityTemplate( sourceSectionType.getTailTemplate() );
962                            final VelocityContext ctx = getVelocityContext();
963                            ctx.put( "template", template );
964                            template.merge( ctx, writer );
965                            writer.close();
966                            s.getTailContent().setLength( 0 );
967                            s.getTailContent().append( writer.toString() );
968                        }
969                    }
970                    else
971                    {
972                        this.getUnknownSections().add( s );
973                    }
974                }
975            }
976    
977            private void createSections( final SourceSectionsType sourceSectionsType, final Section section )
978            {
979                if ( sourceSectionsType != null && section != null )
980                {
981                    for ( SourceSectionType sourceSectionType : sourceSectionsType.getSourceSection() )
982                    {
983                        Section childSection = section.getSection( sourceSectionType.getName() );
984    
985                        if ( childSection == null && !sourceSectionType.isOptional() )
986                        {
987                            childSection = new Section();
988                            childSection.setName( sourceSectionType.getName() );
989                            childSection.setStartingLine( getIndentation( sourceSectionType.getIndentationLevel() )
990                                                          + "// SECTION-START[" + sourceSectionType.getName() + "]" );
991    
992                            childSection.setEndingLine( getIndentation( sourceSectionType.getIndentationLevel() )
993                                                        + "// SECTION-END" );
994    
995                            section.getSections().add( childSection );
996                            this.getAddedSections().add( childSection );
997                        }
998    
999                        this.createSections( sourceSectionType.getSourceSections(), childSection );
1000                    }
1001                }
1002            }
1003    
1004        }
1005    }