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