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: SourceFileProcessor.java 4352 2012-03-01 11:19:48Z schulte2005 $
029     *
030     */
031    package org.jomc.tools;
032    
033    import java.io.File;
034    import java.io.IOException;
035    import java.io.RandomAccessFile;
036    import java.io.StringWriter;
037    import java.nio.ByteBuffer;
038    import java.nio.channels.FileChannel;
039    import java.nio.channels.FileLock;
040    import java.text.MessageFormat;
041    import java.util.LinkedList;
042    import java.util.List;
043    import java.util.ResourceBundle;
044    import java.util.logging.Level;
045    import org.apache.commons.lang.StringUtils;
046    import org.apache.velocity.Template;
047    import org.apache.velocity.VelocityContext;
048    import org.apache.velocity.exception.VelocityException;
049    import org.jomc.model.Implementation;
050    import org.jomc.model.Implementations;
051    import org.jomc.model.Instance;
052    import org.jomc.model.Module;
053    import org.jomc.model.Specification;
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 $JOMC: SourceFileProcessor.java 4352 2012-03-01 11:19:48Z schulte2005 $
075     */
076    public class SourceFileProcessor extends JomcTool
077    {
078    
079        /** The source file editor of the instance. */
080        private SourceFileProcessor.SourceFileEditor sourceFileEditor;
081    
082        /** Source files model. */
083        @Deprecated
084        private SourceFilesType sourceFilesType;
085    
086        /** Creates a new {@code SourceFileProcessor} instance. */
087        public SourceFileProcessor()
088        {
089            super();
090        }
091    
092        /**
093         * Creates a new {@code SourceFileProcessor} instance taking a {@code SourceFileProcessor} instance to initialize
094         * the instance with.
095         *
096         * @param tool The instance to initialize the new instance with,
097         *
098         * @throws NullPointerException if {@code tool} is {@code null}.
099         * @throws IOException if copying {@code tool} fails.
100         */
101        public SourceFileProcessor( final SourceFileProcessor tool ) throws IOException
102        {
103            super( tool );
104            this.sourceFilesType = tool.sourceFilesType != null ? tool.sourceFilesType.clone() : null;
105            this.sourceFileEditor = tool.sourceFileEditor;
106        }
107    
108        /**
109         * Gets the source files model of the instance.
110         * <p>This accessor method returns a reference to the live object, not a snapshot. Therefore any modification you
111         * make to the returned object will be present inside the object. This is why there is no {@code set} method.</p>
112         *
113         * @return The source files model of the instance.
114         *
115         * @see #getSourceFileType(org.jomc.model.Specification)
116         * @see #getSourceFileType(org.jomc.model.Implementation)
117         *
118         * @deprecated As of JOMC 1.2, please add source file models to {@code Specification}s and {@code Implementation}s
119         * directly. This method will be removed in version 2.0.
120         */
121        @Deprecated
122        public SourceFilesType getSourceFilesType()
123        {
124            if ( this.sourceFilesType == null )
125            {
126                this.sourceFilesType = new SourceFilesType();
127            }
128    
129            return this.sourceFilesType;
130        }
131    
132        /**
133         * Gets the model of a specification source file of the modules of the instance.
134         *
135         * @param specification The specification to get a source file model for.
136         *
137         * @return The source file model for {@code specification}. As of JOMC 1.2, this method returns {@code null} if no
138         * source file model is found.
139         *
140         * @throws NullPointerException if {@code specification} is {@code null}.
141         *
142         * @deprecated As of JOMC 1.2, please use method {@link #getSourceFilesType(org.jomc.model.Specification)}. This
143         * method will be removed in version 2.0.
144         */
145        @Deprecated
146        public SourceFileType getSourceFileType( final Specification specification )
147        {
148            if ( specification == null )
149            {
150                throw new NullPointerException( "specification" );
151            }
152    
153            assert this.getModules().getSpecification( specification.getIdentifier() ) != null :
154                "Specification '" + specification.getIdentifier() + "' not found.";
155    
156            SourceFileType sourceFileType = this.getSourceFilesType().getSourceFile( specification.getIdentifier() );
157    
158            if ( sourceFileType == null )
159            {
160                sourceFileType = specification.getAnyObject( SourceFileType.class );
161            }
162    
163            return sourceFileType;
164        }
165    
166        /**
167         * Gets the source files model of a specification of the modules of the instance.
168         *
169         * @param specification The specification to get a source files model for.
170         *
171         * @return The source files model for {@code specification} or {@code null}, if no source files model is found.
172         *
173         * @throws NullPointerException if {@code specification} is {@code null}.
174         *
175         * @since 1.2
176         */
177        public SourceFilesType getSourceFilesType( final Specification specification )
178        {
179            if ( specification == null )
180            {
181                throw new NullPointerException( "specification" );
182            }
183    
184            assert this.getModules().getSpecification( specification.getIdentifier() ) != null :
185                "Specification '" + specification.getIdentifier() + "' not found.";
186    
187            SourceFilesType model = null;
188            final SourceFileType sourceFileType = this.getSourceFileType( specification );
189    
190            if ( sourceFileType != null )
191            {
192                model = new SourceFilesType();
193                model.getSourceFile().add( sourceFileType );
194            }
195            else
196            {
197                model = specification.getAnyObject( SourceFilesType.class );
198            }
199    
200            return model;
201        }
202    
203        /**
204         * Gets the model of an implementation source file of the modules of the instance.
205         *
206         * @param implementation The implementation to get a source file model for.
207         *
208         * @return The source file model for {@code implementation}. As of JOMC 1.2, this method returns {@code null} if no
209         * source file model is found.
210         *
211         * @throws NullPointerException if {@code implementation} is {@code null}.
212         *
213         * @deprecated As of JOMC 1.2, please use method {@link #getSourceFilesType(org.jomc.model.Implementation)}. This
214         * method will be removed in version 2.0.
215         */
216        @Deprecated
217        public SourceFileType getSourceFileType( final Implementation implementation )
218        {
219            if ( implementation == null )
220            {
221                throw new NullPointerException( "implementation" );
222            }
223    
224            assert this.getModules().getImplementation( implementation.getIdentifier() ) != null :
225                "Implementation '" + implementation.getIdentifier() + "' not found.";
226    
227            SourceFileType sourceFileType = this.getSourceFilesType().getSourceFile( implementation.getIdentifier() );
228    
229            if ( sourceFileType == null )
230            {
231                sourceFileType = implementation.getAnyObject( SourceFileType.class );
232            }
233    
234            return sourceFileType;
235        }
236    
237        /**
238         * Gets the source files model of an implementation of the modules of the instance.
239         *
240         * @param implementation The implementation to get a source files model for.
241         *
242         * @return The source files model for {@code implementation} or {@code null}, if no source files model is found.
243         *
244         * @throws NullPointerException if {@code implementation} is {@code null}.
245         *
246         * @since 1.2
247         */
248        public SourceFilesType getSourceFilesType( final Implementation implementation )
249        {
250            if ( implementation == null )
251            {
252                throw new NullPointerException( "implementation" );
253            }
254    
255            assert this.getModules().getImplementation( implementation.getIdentifier() ) != null :
256                "Implementation '" + implementation.getIdentifier() + "' not found.";
257    
258            SourceFilesType model = null;
259            final SourceFileType sourceFileType = this.getSourceFileType( implementation );
260    
261            if ( sourceFileType != null )
262            {
263                model = new SourceFilesType();
264                model.getSourceFile().add( sourceFileType );
265            }
266            else
267            {
268                final Instance instance = this.getModules().getInstance( implementation.getIdentifier() );
269                assert instance != null : "Instance '" + implementation.getIdentifier() + "' not found.";
270                model = instance.getAnyObject( SourceFilesType.class );
271            }
272    
273            return model;
274        }
275    
276        /**
277         * Gets the source file editor of the instance.
278         *
279         * @return The source file editor of the instance.
280         *
281         * @since 1.2
282         *
283         * @see #setSourceFileEditor(org.jomc.tools.SourceFileProcessor.SourceFileEditor)
284         */
285        public final SourceFileProcessor.SourceFileEditor getSourceFileEditor()
286        {
287            if ( this.sourceFileEditor == null )
288            {
289                this.sourceFileEditor =
290                    new SourceFileProcessor.SourceFileEditor( new TrailingWhitespaceEditor( this.getLineSeparator() ),
291                                                              this.getLineSeparator() );
292    
293            }
294    
295            return this.sourceFileEditor;
296        }
297    
298        /**
299         * Sets the source file editor of the instance.
300         *
301         * @param value The new source file editor of the instance or {@code null}.
302         *
303         * @since 1.2
304         *
305         * @see #getSourceFileEditor()
306         */
307        public final void setSourceFileEditor( final SourceFileProcessor.SourceFileEditor value )
308        {
309            this.sourceFileEditor = value;
310        }
311    
312        /**
313         * Gets a new editor for editing the source file of a given specification of the modules of the instance.
314         *
315         * @param specification The specification whose source file to edit.
316         *
317         * @return A new editor for editing the source file of {@code specification}.
318         *
319         * @throws NullPointerException if {@code specification} is {@code null}.
320         *
321         * @deprecated As of JOMC 1.2, please use method {@link #getSourceFileEditor()}. This method will be removed in
322         * version 2.0.
323         *
324         * @see SourceFileEditor#edit(org.jomc.model.Specification, org.jomc.tools.model.SourceFileType, java.io.File)
325         */
326        @Deprecated
327        public SourceFileProcessor.SourceFileEditor getSourceFileEditor( final Specification specification )
328        {
329            if ( specification == null )
330            {
331                throw new NullPointerException( "specification" );
332            }
333    
334            assert this.getModules().getSpecification( specification.getIdentifier() ) != null :
335                "Specification '" + specification.getIdentifier() + "' not found.";
336    
337            return this.getSourceFileEditor();
338        }
339    
340        /**
341         * Gets a new editor for editing the source file of a given implementation of the modules of the instance.
342         *
343         * @param implementation The implementation whose source file to edit.
344         *
345         * @return A new editor for editing the source file of {@code implementation}.
346         *
347         * @throws NullPointerException if {@code implementation} is {@code null}.
348         *
349         * @deprecated As of JOMC 1.2, please use method {@link #getSourceFileEditor()}. This method will be removed in
350         * version 2.0.
351         *
352         * @see SourceFileEditor#edit(org.jomc.model.Implementation, org.jomc.tools.model.SourceFileType, java.io.File)
353         */
354        @Deprecated
355        public SourceFileProcessor.SourceFileEditor getSourceFileEditor( final Implementation implementation )
356        {
357            if ( implementation == null )
358            {
359                throw new NullPointerException( "implementation" );
360            }
361    
362            assert this.getModules().getImplementation( implementation.getIdentifier() ) != null :
363                "Implementation '" + implementation.getIdentifier() + "' not found.";
364    
365            return this.getSourceFileEditor();
366        }
367    
368        /**
369         * Manages the source files of the modules of the instance.
370         *
371         * @param sourcesDirectory The directory holding the source files to manage.
372         *
373         * @throws NullPointerException if {@code sourcesDirectory} is {@code null}.
374         * @throws IOException if managing source files fails.
375         *
376         * @see #manageSourceFiles(org.jomc.model.Module, java.io.File)
377         */
378        public void manageSourceFiles( final File sourcesDirectory ) throws IOException
379        {
380            if ( sourcesDirectory == null )
381            {
382                throw new NullPointerException( "sourcesDirectory" );
383            }
384    
385            for ( int i = this.getModules().getModule().size() - 1; i >= 0; i-- )
386            {
387                this.manageSourceFiles( this.getModules().getModule().get( i ), sourcesDirectory );
388            }
389        }
390    
391        /**
392         * Manages the source files of a given module of the modules of the instance.
393         *
394         * @param module The module to process.
395         * @param sourcesDirectory The directory holding the source files to manage.
396         *
397         * @throws NullPointerException if {@code module} or {@code sourcesDirectory} is {@code null}.
398         * @throws IOException if managing source files fails.
399         *
400         * @see #manageSourceFiles(org.jomc.model.Specification, java.io.File)
401         * @see #manageSourceFiles(org.jomc.model.Implementation, java.io.File)
402         */
403        public void manageSourceFiles( final Module module, final File sourcesDirectory ) throws IOException
404        {
405            if ( module == null )
406            {
407                throw new NullPointerException( "module" );
408            }
409            if ( sourcesDirectory == null )
410            {
411                throw new NullPointerException( "sourcesDirectory" );
412            }
413    
414            assert this.getModules().getModule( module.getName() ) != null : "Module '" + module.getName() + "' not found.";
415    
416            if ( module.getSpecifications() != null )
417            {
418                for ( int i = 0, s0 = module.getSpecifications().getSpecification().size(); i < s0; i++ )
419                {
420                    this.manageSourceFiles( module.getSpecifications().getSpecification().get( i ), sourcesDirectory );
421                }
422            }
423            if ( module.getImplementations() != null )
424            {
425                for ( int i = 0, s0 = module.getImplementations().getImplementation().size(); i < s0; i++ )
426                {
427                    this.manageSourceFiles( module.getImplementations().getImplementation().get( i ), sourcesDirectory );
428                }
429            }
430        }
431    
432        /**
433         * Manages the source files of a given specification of the modules of the instance.
434         *
435         * @param specification The specification to process.
436         * @param sourcesDirectory The directory holding the source files to manage.
437         *
438         * @throws NullPointerException if {@code specification} or {@code sourcesDirectory} is {@code null}.
439         * @throws IOException if managing source files fails.
440         *
441         * @see #getSourceFileEditor()
442         * @see #getSourceFilesType(org.jomc.model.Specification)
443         */
444        public void manageSourceFiles( final Specification specification, final File sourcesDirectory ) throws IOException
445        {
446            if ( specification == null )
447            {
448                throw new NullPointerException( "specification" );
449            }
450            if ( sourcesDirectory == null )
451            {
452                throw new NullPointerException( "sourcesDirectory" );
453            }
454    
455            assert this.getModules().getSpecification( specification.getIdentifier() ) != null :
456                "Specification '" + specification.getIdentifier() + "' not found.";
457    
458            if ( specification.isClassDeclaration() )
459            {
460                boolean manage = true;
461                final Implementations implementations = this.getModules().getImplementations();
462    
463                if ( implementations != null )
464                {
465                    for ( int i = 0, s0 = implementations.getImplementation().size(); i < s0; i++ )
466                    {
467                        final Implementation impl = implementations.getImplementation().get( i );
468    
469                        if ( impl.isClassDeclaration() && specification.getClazz().equals( impl.getClazz() ) )
470                        {
471                            this.manageSourceFiles( impl, sourcesDirectory );
472                            manage = false;
473                            break;
474                        }
475                    }
476                }
477    
478                if ( manage )
479                {
480                    final SourceFileProcessor.SourceFileEditor editor = this.getSourceFileEditor( specification );
481                    final SourceFilesType model = this.getSourceFilesType( specification );
482    
483                    if ( editor != null && model != null )
484                    {
485                        for ( int i = 0, s0 = model.getSourceFile().size(); i < s0; i++ )
486                        {
487                            editor.edit( specification, model.getSourceFile().get( i ), sourcesDirectory );
488                        }
489                    }
490                }
491            }
492        }
493    
494        /**
495         * Manages the source files of a given implementation of the modules of the instance.
496         *
497         * @param implementation The implementation to process.
498         * @param sourcesDirectory The directory holding the source files to manage.
499         *
500         * @throws NullPointerException if {@code implementation} or {@code sourcesDirectory} is {@code null}.
501         * @throws IOException if managing source files fails.
502         *
503         * @see #getSourceFileEditor()
504         * @see #getSourceFilesType(org.jomc.model.Implementation)
505         */
506        public void manageSourceFiles( final Implementation implementation, final File sourcesDirectory )
507            throws IOException
508        {
509            if ( implementation == null )
510            {
511                throw new NullPointerException( "implementation" );
512            }
513            if ( sourcesDirectory == null )
514            {
515                throw new NullPointerException( "sourcesDirectory" );
516            }
517    
518            assert this.getModules().getImplementation( implementation.getIdentifier() ) != null :
519                "Implementation '" + implementation.getIdentifier() + "' not found.";
520    
521            if ( implementation.isClassDeclaration() )
522            {
523                final SourceFileProcessor.SourceFileEditor editor = this.getSourceFileEditor( implementation );
524                final SourceFilesType model = this.getSourceFilesType( implementation );
525    
526                if ( editor != null && model != null )
527                {
528                    for ( int i = 0, s0 = model.getSourceFile().size(); i < s0; i++ )
529                    {
530                        editor.edit( implementation, model.getSourceFile().get( i ), sourcesDirectory );
531                    }
532                }
533            }
534        }
535    
536        private static String getMessage( final String key, final Object... arguments )
537        {
538            if ( key == null )
539            {
540                throw new NullPointerException( "key" );
541            }
542    
543            return MessageFormat.format( ResourceBundle.getBundle(
544                SourceFileProcessor.class.getName().replace( '.', '/' ) ).getString( key ), arguments );
545    
546        }
547    
548        private static String getMessage( final Throwable t )
549        {
550            return t != null ? t.getMessage() != null ? t.getMessage() : getMessage( t.getCause() ) : null;
551        }
552    
553        /**
554         * Extension to {@code SectionEditor} adding support for editing source code files.
555         *
556         * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a>
557         * @version $JOMC: SourceFileProcessor.java 4352 2012-03-01 11:19:48Z schulte2005 $
558         *
559         * @see #edit(org.jomc.model.Specification, org.jomc.tools.model.SourceFileType, java.io.File)
560         * @see #edit(org.jomc.model.Implementation, org.jomc.tools.model.SourceFileType, java.io.File)
561         */
562        public class SourceFileEditor extends SectionEditor
563        {
564    
565            /** {@code Specification} of the instance or {@code null}. */
566            private Specification specification;
567    
568            /** {@code Implementation} of the instance or {@code null}. */
569            private Implementation implementation;
570    
571            /** The source code file to edit. */
572            private SourceFileType sourceFileType;
573    
574            /** The {@code VelocityContext} of the instance. */
575            private VelocityContext velocityContext;
576    
577            /** List of sections added to the input. */
578            @Deprecated
579            private List<Section> addedSections;
580    
581            /** List of sections without corresponding model entry. */
582            @Deprecated
583            private List<Section> unknownSections;
584    
585            /**
586             * Creates a new {@code SourceFileEditor} instance.
587             *
588             * @since 1.2
589             */
590            public SourceFileEditor()
591            {
592                this( (LineEditor) null, (String) null );
593            }
594    
595            /**
596             * Creates a new {@code SourceFileEditor} instance taking a string to use for separating lines.
597             *
598             * @param lineSeparator String to use for separating lines.
599             *
600             * @since 1.2
601             */
602            public SourceFileEditor( final String lineSeparator )
603            {
604                this( (LineEditor) null, lineSeparator );
605            }
606    
607            /**
608             * Creates a new {@code SourceFileEditor} instance taking an editor to chain.
609             *
610             * @param editor The editor to chain.
611             *
612             * @since 1.2
613             */
614            public SourceFileEditor( final LineEditor editor )
615            {
616                this( editor, null );
617            }
618    
619            /**
620             * Creates a new {@code SourceFileEditor} instance taking an editor to chain and a string to use for separating
621             * lines.
622             *
623             * @param editor The editor to chain.
624             * @param lineSeparator String to use for separating lines.
625             *
626             * @since 1.2
627             */
628            public SourceFileEditor( final LineEditor editor, final String lineSeparator )
629            {
630                super( editor, lineSeparator );
631            }
632    
633            /**
634             * Creates a new {@code SourceFileEditor} taking a {@code Specification} to edit source code of.
635             *
636             * @param specification The specification to edit source code of.
637             *
638             * @deprecated As of JOMC 1.2, please use method {@link #edit(org.jomc.model.Specification, org.jomc.tools.model.SourceFileType, java.io.File)}.
639             * This constructor will be removed in version 2.0.
640             */
641            @Deprecated
642            public SourceFileEditor( final Specification specification )
643            {
644                this( specification, null, null );
645            }
646    
647            /**
648             * Creates a new {@code SourceFileEditor} taking a {@code Specification} to edit source code of and a line
649             * separator.
650             *
651             * @param specification The specification to edit source code of.
652             * @param lineSeparator The line separator of the editor.
653             *
654             * @deprecated As of JOMC 1.2, please use method {@link #edit(org.jomc.model.Specification, org.jomc.tools.model.SourceFileType, java.io.File)}.
655             * This constructor will be removed in version 2.0.
656             */
657            @Deprecated
658            public SourceFileEditor( final Specification specification, final String lineSeparator )
659            {
660                this( specification, null, lineSeparator );
661            }
662    
663            /**
664             * Creates a new {@code SourceFileEditor} taking a {@code Specification} to edit source code of and an editor to
665             * chain.
666             *
667             * @param specification The specification backing the editor.
668             * @param lineEditor The editor to chain.
669             *
670             * @deprecated As of JOMC 1.2, please use method {@link #edit(org.jomc.model.Specification, org.jomc.tools.model.SourceFileType, java.io.File)}.
671             * This constructor will be removed in version 2.0.
672             */
673            @Deprecated
674            public SourceFileEditor( final Specification specification, final LineEditor lineEditor )
675            {
676                this( specification, lineEditor, null );
677            }
678    
679            /**
680             * Creates a new {@code SourceFileEditor} taking a {@code Specification} to edit source code of, an editor to
681             * chain and a line separator.
682             *
683             * @param specification The specification backing the editor.
684             * @param lineEditor The editor to chain.
685             * @param lineSeparator The line separator of the editor.
686             *
687             * @deprecated As of JOMC 1.2, please use method {@link #edit(org.jomc.model.Specification, org.jomc.tools.model.SourceFileType, java.io.File)}.
688             * This constructor will be removed in version 2.0.
689             */
690            @Deprecated
691            public SourceFileEditor( final Specification specification, final LineEditor lineEditor,
692                                     final String lineSeparator )
693            {
694                super( lineEditor, lineSeparator );
695                this.specification = specification;
696                this.implementation = null;
697                this.sourceFileType = null;
698                this.velocityContext = null;
699    
700                assert getModules().getSpecification( specification.getIdentifier() ) != null :
701                    "Specification '" + specification.getIdentifier() + "' not found.";
702    
703            }
704    
705            /**
706             * Creates a new {@code SourceFileEditor} taking an {@code Implementation} to edit source code of.
707             *
708             * @param implementation The implementation to edit source code of.
709             *
710             * @deprecated As of JOMC 1.2, please use method {@link #edit(org.jomc.model.Implementation, org.jomc.tools.model.SourceFileType, java.io.File)}.
711             * This constructor will be removed in version 2.0.
712             */
713            @Deprecated
714            public SourceFileEditor( final Implementation implementation )
715            {
716                this( implementation, null, null );
717            }
718    
719            /**
720             * Creates a new {@code SourceFileEditor} taking an {@code Implementation} to edit source code of and a line
721             * separator.
722             *
723             * @param implementation The implementation to edit source code of.
724             * @param lineSeparator The line separator of the editor.
725             *
726             * @deprecated As of JOMC 1.2, please use method {@link #edit(org.jomc.model.Implementation, org.jomc.tools.model.SourceFileType, java.io.File)}.
727             * This constructor will be removed in version 2.0.
728             */
729            @Deprecated
730            public SourceFileEditor( final Implementation implementation, final String lineSeparator )
731            {
732                this( implementation, null, lineSeparator );
733            }
734    
735            /**
736             * Creates a new {@code SourceFileEditor} taking an {@code Implementation} to edit source code of and an editor
737             * to chain.
738             *
739             * @param implementation The implementation to edit source code of.
740             * @param lineEditor The editor to chain.
741             *
742             * @deprecated As of JOMC 1.2, please use method {@link #edit(org.jomc.model.Implementation, org.jomc.tools.model.SourceFileType, java.io.File)}.
743             * This constructor will be removed in version 2.0.
744             */
745            @Deprecated
746            public SourceFileEditor( final Implementation implementation, final LineEditor lineEditor )
747            {
748                this( implementation, lineEditor, null );
749            }
750    
751            /**
752             * Creates a new {@code SourceFileEditor} taking an {@code Implementation} to edit source code of, an editor
753             * to chain and a line separator.
754             *
755             * @param implementation The implementation to edit source code of.
756             * @param lineEditor The editor to chain.
757             * @param lineSeparator The line separator of the editor.
758             *
759             * @deprecated As of JOMC 1.2, please use method {@link #edit(org.jomc.model.Implementation, org.jomc.tools.model.SourceFileType, java.io.File)}.
760             * This constructor will be removed in version 2.0.
761             */
762            @Deprecated
763            public SourceFileEditor( final Implementation implementation, final LineEditor lineEditor,
764                                     final String lineSeparator )
765            {
766                super( lineEditor, lineSeparator );
767                this.implementation = implementation;
768                this.specification = null;
769                this.sourceFileType = null;
770                this.velocityContext = null;
771    
772                assert getModules().getImplementation( implementation.getIdentifier() ) != null :
773                    "Implementation '" + implementation.getIdentifier() + "' not found.";
774    
775            }
776    
777            /**
778             * Edits a source file of a given specification.
779             *
780             * @param specification The specification to edit a source file of.
781             * @param sourceFileType The model of the source file to edit.
782             * @param sourcesDirectory The directory holding the source file to edit.
783             *
784             * @throws NullPointerException if {@code specification}, {@code sourceFileType} or {@code sourcesDirectory} is
785             * {@code null}.
786             * @throws IOException if editing fails.
787             *
788             * @since 1.2
789             */
790            public final void edit( final Specification specification, final SourceFileType sourceFileType,
791                                    final File sourcesDirectory ) throws IOException
792            {
793                if ( specification == null )
794                {
795                    throw new NullPointerException( "specification" );
796                }
797                if ( sourceFileType == null )
798                {
799                    throw new NullPointerException( "sourceFileType" );
800                }
801                if ( sourcesDirectory == null )
802                {
803                    throw new NullPointerException( "sourcesDirectory" );
804                }
805    
806                assert getModules().getSpecification( specification.getIdentifier() ) != null :
807                    "Specification '" + specification.getIdentifier() + "' not found.";
808    
809                this.specification = specification;
810                this.sourceFileType = sourceFileType;
811                this.velocityContext = SourceFileProcessor.this.getVelocityContext();
812                this.velocityContext.put( "specification", specification );
813    
814                this.editSourceFile( sourcesDirectory );
815    
816                this.implementation = null;
817                this.specification = null;
818                this.sourceFileType = null;
819                this.velocityContext = null;
820            }
821    
822            /**
823             * Edits a source file of a given implementation.
824             *
825             * @param implementation The implementation to edit a source file of.
826             * @param sourceFileType The model of the source file to edit.
827             * @param sourcesDirectory The directory holding the source file to edit.
828             *
829             * @throws NullPointerException if {@code implementation}, {@code sourceFileType} or {@code sourcesDirectory} is
830             * {@code null}.
831             * @throws IOException if editing fails.
832             *
833             * @since 1.2
834             */
835            public final void edit( final Implementation implementation, final SourceFileType sourceFileType,
836                                    final File sourcesDirectory ) throws IOException
837            {
838                if ( implementation == null )
839                {
840                    throw new NullPointerException( "implementation" );
841                }
842                if ( sourceFileType == null )
843                {
844                    throw new NullPointerException( "sourceFileType" );
845                }
846                if ( sourcesDirectory == null )
847                {
848                    throw new NullPointerException( "sourcesDirectory" );
849                }
850    
851                assert getModules().getImplementation( implementation.getIdentifier() ) != null :
852                    "Implementation '" + implementation.getIdentifier() + "' not found.";
853    
854                this.implementation = implementation;
855                this.sourceFileType = sourceFileType;
856                this.velocityContext = SourceFileProcessor.this.getVelocityContext();
857                this.velocityContext.put( "implementation", implementation );
858    
859                this.editSourceFile( sourcesDirectory );
860    
861                this.implementation = null;
862                this.specification = null;
863                this.sourceFileType = null;
864                this.velocityContext = null;
865            }
866    
867            /**
868             * Gets a list of sections added to the input.
869             * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you
870             * make to the returned list will be present inside the object. This is why there is no {@code set} method
871             * for the added sections property.</p>
872             *
873             * @return A list of sections added to the input.
874             *
875             * @deprecated As of JOMC 1.2, deprecated without replacement. This method will be removed in version 2.0.
876             */
877            @Deprecated
878            public List<Section> getAddedSections()
879            {
880                if ( this.addedSections == null )
881                {
882                    this.addedSections = new LinkedList<Section>();
883                }
884    
885                return this.addedSections;
886            }
887    
888            /**
889             * Gets a list of sections without corresponding model entry.
890             * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you
891             * make to the returned list will be present inside the object. This is why there is no {@code set} method
892             * for the unknown sections property.</p>
893             *
894             * @return A list of sections without corresponding model entry.
895             *
896             * @deprecated As of JOMC 1.2, deprecated without replacement. This method will be removed in version 2.0.
897             */
898            @Deprecated
899            public List<Section> getUnknownSections()
900            {
901                if ( this.unknownSections == null )
902                {
903                    this.unknownSections = new LinkedList<Section>();
904                }
905    
906                return this.unknownSections;
907            }
908    
909            /**
910             * Gets the currently edited source code file.
911             *
912             * @return The currently edited source code file.
913             *
914             * @deprecated As of JOMC 1.2, deprecated without replacement. This method will be removed in version 2.0.
915             */
916            @Deprecated
917            protected SourceFileType getSourceFileType()
918            {
919                if ( this.sourceFileType == null )
920                {
921                    if ( this.specification != null )
922                    {
923                        return SourceFileProcessor.this.getSourceFileType( this.specification );
924                    }
925    
926                    if ( this.implementation != null )
927                    {
928                        return SourceFileProcessor.this.getSourceFileType( this.implementation );
929                    }
930                }
931    
932                return this.sourceFileType;
933            }
934    
935            /**
936             * Gets a new velocity context used for merging templates.
937             *
938             * @return A new velocity context used for merging templates.
939             *
940             * @deprecated As of JOMC 1.2, deprecated without replacement. This method will be removed in version 2.0.
941             */
942            @Deprecated
943            protected VelocityContext getVelocityContext()
944            {
945                if ( this.velocityContext == null )
946                {
947                    final VelocityContext ctx = SourceFileProcessor.this.getVelocityContext();
948    
949                    if ( this.specification != null )
950                    {
951                        ctx.put( "specification", this.specification );
952                    }
953    
954                    if ( this.implementation != null )
955                    {
956                        ctx.put( "implementation", this.implementation );
957                    }
958    
959                    return ctx;
960                }
961    
962                return this.velocityContext;
963            }
964    
965            /**
966             * {@inheritDoc}
967             * <p>This method creates any sections declared in the model of the source file as returned by method
968             * {@code getSourceFileType} prior to rendering the output of the editor.</p>
969             *
970             * @param section The section to start rendering the editor's output with.
971             *
972             * @see #createSection(java.lang.String, java.lang.String, org.jomc.tools.model.SourceSectionType)
973             */
974            @Override
975            protected String getOutput( final Section section ) throws IOException
976            {
977                this.getAddedSections().clear();
978                this.getUnknownSections().clear();
979    
980                final SourceFileType model = this.getSourceFileType();
981    
982                if ( model != null )
983                {
984                    this.createSections( model, model.getSourceSections(), section );
985                }
986    
987                return super.getOutput( section );
988            }
989    
990            /**
991             * {@inheritDoc}
992             * <p>This method searches the model of the source file for a section matching {@code s} and updates properties
993             * {@code headContent} and {@code tailContent} of {@code s} according to the templates declared in the model
994             * as returned by method {@code getSourceFileType}.</p>
995             *
996             * @param s The section to edit.
997             */
998            @Override
999            protected void editSection( final Section s ) throws IOException
1000            {
1001                try
1002                {
1003                    super.editSection( s );
1004    
1005                    final SourceFileType model = this.getSourceFileType();
1006    
1007                    if ( s.getName() != null && model != null && model.getSourceSections() != null )
1008                    {
1009                        final SourceSectionType sourceSectionType =
1010                            model.getSourceSections().getSourceSection( s.getName() );
1011    
1012                        if ( sourceSectionType != null )
1013                        {
1014                            if ( s.getStartingLine() != null )
1015                            {
1016                                s.setStartingLine( getIndentation( sourceSectionType.getIndentationLevel() )
1017                                                   + s.getStartingLine().trim() );
1018    
1019                            }
1020                            if ( s.getEndingLine() != null )
1021                            {
1022                                s.setEndingLine( getIndentation( sourceSectionType.getIndentationLevel() )
1023                                                 + s.getEndingLine().trim() );
1024    
1025                            }
1026    
1027                            if ( sourceSectionType.getHeadTemplate() != null
1028                                 && ( !sourceSectionType.isEditable()
1029                                      || s.getHeadContent().toString().trim().length() == 0 ) )
1030                            {
1031                                final StringWriter writer = new StringWriter();
1032                                final Template template = getVelocityTemplate( sourceSectionType.getHeadTemplate() );
1033                                final VelocityContext ctx = getVelocityContext();
1034                                ctx.put( "template", template );
1035                                template.merge( ctx, writer );
1036                                writer.close();
1037                                s.getHeadContent().setLength( 0 );
1038                                s.getHeadContent().append( writer.toString() );
1039                            }
1040    
1041                            if ( sourceSectionType.getTailTemplate() != null
1042                                 && ( !sourceSectionType.isEditable()
1043                                      || s.getTailContent().toString().trim().length() == 0 ) )
1044                            {
1045                                final StringWriter writer = new StringWriter();
1046                                final Template template = getVelocityTemplate( sourceSectionType.getTailTemplate() );
1047                                final VelocityContext ctx = getVelocityContext();
1048                                ctx.put( "template", template );
1049                                template.merge( ctx, writer );
1050                                writer.close();
1051                                s.getTailContent().setLength( 0 );
1052                                s.getTailContent().append( writer.toString() );
1053                            }
1054                        }
1055                        else
1056                        {
1057                            if ( isLoggable( Level.WARNING ) )
1058                            {
1059                                if ( this.implementation != null )
1060                                {
1061                                    log( Level.WARNING, getMessage(
1062                                        "unknownImplementationSection", this.implementation.getIdentifier(),
1063                                        model.getIdentifier(), s.getName() ), null );
1064    
1065    
1066                                }
1067                                else if ( this.specification != null )
1068                                {
1069                                    log( Level.WARNING, getMessage(
1070                                        "unknownSpecificationSection", this.specification.getIdentifier(),
1071                                        model.getIdentifier(), s.getName() ), null );
1072    
1073                                }
1074                            }
1075    
1076                            this.getUnknownSections().add( s );
1077                        }
1078                    }
1079                }
1080                catch ( final VelocityException e )
1081                {
1082                    // JDK: As of JDK 6, "new IOException( message, cause )".
1083                    throw (IOException) new IOException( getMessage( e ) ).initCause( e );
1084                }
1085            }
1086    
1087            private void createSections( final SourceFileType sourceFileType, final SourceSectionsType sourceSectionsType,
1088                                         final Section section ) throws IOException
1089            {
1090                if ( sourceSectionsType != null && section != null )
1091                {
1092                    for ( int i = 0, s0 = sourceSectionsType.getSourceSection().size(); i < s0; i++ )
1093                    {
1094                        final SourceSectionType sourceSectionType = sourceSectionsType.getSourceSection().get( i );
1095                        Section childSection = section.getSection( sourceSectionType.getName() );
1096    
1097                        if ( childSection == null && !sourceSectionType.isOptional() )
1098                        {
1099                            childSection = this.createSection( StringUtils.defaultString( sourceFileType.getHeadComment() ),
1100                                                               StringUtils.defaultString( sourceFileType.getTailComment() ),
1101                                                               sourceSectionType );
1102    
1103                            section.getSections().add( childSection );
1104    
1105                            if ( isLoggable( Level.FINE ) )
1106                            {
1107                                log( Level.FINE, getMessage(
1108                                    "addedSection", sourceFileType.getIdentifier(), childSection.getName() ), null );
1109    
1110                            }
1111    
1112                            this.getAddedSections().add( childSection );
1113                        }
1114    
1115                        this.createSections( sourceFileType, sourceSectionType.getSourceSections(), childSection );
1116                    }
1117                }
1118            }
1119    
1120            /**
1121             * Creates a new {@code Section} instance for a given {@code SourceSectionType}.
1122             *
1123             * @param headComment Characters to use to start a comment in the source file.
1124             * @param tailComment Characters to use to end a comment in the source file.
1125             * @param sourceSectionType The {@code SourceSectionType} to create a new {@code Section} instance for.
1126             *
1127             * @return A new {@code Section} instance for {@code sourceSectionType}.
1128             *
1129             * @throws NullPointerException if {@code headComment}, {@code tailComment} or {@code sourceSectionType} is
1130             * {@code null}.
1131             * @throws IOException if creating a new {@code Section} instance fails.
1132             *
1133             * @since 1.2
1134             */
1135            private Section createSection( final String headComment, final String tailComment,
1136                                           final SourceSectionType sourceSectionType ) throws IOException
1137            {
1138                if ( headComment == null )
1139                {
1140                    throw new NullPointerException( "headComment" );
1141                }
1142                if ( tailComment == null )
1143                {
1144                    throw new NullPointerException( "tailComment" );
1145                }
1146                if ( sourceSectionType == null )
1147                {
1148                    throw new NullPointerException( "sourceSectionType" );
1149                }
1150    
1151                final Section s = new Section();
1152                s.setName( sourceSectionType.getName() );
1153    
1154                final StringBuilder head = new StringBuilder( 255 );
1155                head.append( getIndentation( sourceSectionType.getIndentationLevel() ) ).append( headComment );
1156    
1157                s.setStartingLine( head + " SECTION-START[" + sourceSectionType.getName() + ']' + tailComment );
1158                s.setEndingLine( head + " SECTION-END" + tailComment );
1159    
1160                return s;
1161            }
1162    
1163            private void editSourceFile( final File sourcesDirectory ) throws IOException
1164            {
1165                if ( sourcesDirectory == null )
1166                {
1167                    throw new NullPointerException( "sourcesDirectory" );
1168                }
1169                if ( !sourcesDirectory.isDirectory() )
1170                {
1171                    throw new IOException( getMessage( "directoryNotFound", sourcesDirectory.getAbsolutePath() ) );
1172                }
1173    
1174                final SourceFileType model = this.getSourceFileType();
1175    
1176                if ( model != null && model.getLocation() != null )
1177                {
1178                    final File f = new File( sourcesDirectory, model.getLocation() );
1179    
1180                    try
1181                    {
1182                        String content = "";
1183                        String edited = null;
1184                        boolean creating = false;
1185    
1186                        if ( !f.exists() )
1187                        {
1188                            if ( model.getTemplate() != null )
1189                            {
1190                                final StringWriter writer = new StringWriter();
1191                                final Template template = getVelocityTemplate( model.getTemplate() );
1192                                final VelocityContext ctx = this.getVelocityContext();
1193                                ctx.put( "template", template );
1194                                template.merge( ctx, writer );
1195                                writer.close();
1196                                content = writer.toString();
1197                                creating = true;
1198                            }
1199                        }
1200                        else
1201                        {
1202                            if ( isLoggable( Level.FINER ) )
1203                            {
1204                                log( Level.FINER, getMessage( "reading", f.getAbsolutePath() ), null );
1205                            }
1206    
1207                            content = this.readSourceFile( f );
1208                        }
1209    
1210                        try
1211                        {
1212                            edited = super.edit( content );
1213                        }
1214                        catch ( final IOException e )
1215                        {
1216                            // JDK: As of JDK 6, "new IOException( message, cause )".
1217                            throw (IOException) new IOException( getMessage(
1218                                "failedEditing", f.getAbsolutePath(), getMessage( e ) ) ).initCause( e );
1219    
1220                        }
1221    
1222                        if ( !edited.equals( content ) || edited.length() == 0 )
1223                        {
1224                            if ( !f.getParentFile().exists() && !f.getParentFile().mkdirs() )
1225                            {
1226                                throw new IOException( getMessage(
1227                                    "failedCreatingDirectory", f.getParentFile().getAbsolutePath() ) );
1228    
1229                            }
1230    
1231                            if ( isLoggable( Level.INFO ) )
1232                            {
1233                                log( Level.INFO, getMessage(
1234                                    creating ? "creating" : "editing", f.getAbsolutePath() ), null );
1235    
1236                            }
1237    
1238                            this.writeSourceFile( f, edited );
1239                        }
1240                        else if ( isLoggable( Level.FINER ) )
1241                        {
1242                            log( Level.FINER, getMessage( "unchanged", f.getAbsolutePath() ), null );
1243                        }
1244                    }
1245                    catch ( final VelocityException e )
1246                    {
1247                        // JDK: As of JDK 6, "new IOException( message, cause )".
1248                        throw (IOException) new IOException( getMessage(
1249                            "failedEditing", f.getAbsolutePath(), getMessage( e ) ) ).initCause( e );
1250    
1251                    }
1252                }
1253            }
1254    
1255            private String readSourceFile( final File file ) throws IOException
1256            {
1257                if ( file == null )
1258                {
1259                    throw new NullPointerException( "file" );
1260                }
1261    
1262                RandomAccessFile randomAccessFile = null;
1263                FileChannel fileChannel = null;
1264                FileLock fileLock = null;
1265                boolean suppressExceptionOnClose = true;
1266    
1267                //final Charset charset = Charset.forName( getInputEncoding() );
1268                final int length = file.length() > 0L ? Long.valueOf( file.length() ).intValue() : 1;
1269                final ByteBuffer buf = ByteBuffer.allocate( length );
1270                final StringBuilder appendable = new StringBuilder( length );
1271    
1272                try
1273                {
1274                    randomAccessFile = new RandomAccessFile( file, "r" );
1275                    fileChannel = randomAccessFile.getChannel();
1276                    fileLock = fileChannel.lock( 0L, file.length(), true );
1277                    fileChannel.position( 0L );
1278    
1279                    buf.clear();
1280                    int read = fileChannel.read( buf );
1281    
1282                    while ( read != -1 )
1283                    {
1284                        // JDK: As of JDK 6, new String( byte[], int, int, Charset )
1285                        appendable.append( new String( buf.array(), buf.arrayOffset(), read, getInputEncoding() ) );
1286                        buf.clear();
1287                        read = fileChannel.read( buf );
1288                    }
1289    
1290                    suppressExceptionOnClose = false;
1291                    return appendable.toString();
1292                }
1293                finally
1294                {
1295                    try
1296                    {
1297                        if ( fileLock != null )
1298                        {
1299                            fileLock.release();
1300                        }
1301                    }
1302                    catch ( final IOException e )
1303                    {
1304                        if ( suppressExceptionOnClose )
1305                        {
1306                            log( Level.SEVERE, null, e );
1307                        }
1308                        else
1309                        {
1310                            throw e;
1311                        }
1312                    }
1313                    finally
1314                    {
1315                        try
1316                        {
1317                            if ( fileChannel != null )
1318                            {
1319                                fileChannel.close();
1320                            }
1321                        }
1322                        catch ( final IOException e )
1323                        {
1324                            if ( suppressExceptionOnClose )
1325                            {
1326                                log( Level.SEVERE, null, e );
1327                            }
1328                            else
1329                            {
1330                                throw e;
1331                            }
1332                        }
1333                        finally
1334                        {
1335                            try
1336                            {
1337                                if ( randomAccessFile != null )
1338                                {
1339                                    randomAccessFile.close();
1340                                }
1341                            }
1342                            catch ( final IOException e )
1343                            {
1344                                if ( suppressExceptionOnClose )
1345                                {
1346                                    log( Level.SEVERE, null, e );
1347                                }
1348                                else
1349                                {
1350                                    throw e;
1351                                }
1352                            }
1353                        }
1354                    }
1355                }
1356            }
1357    
1358            private void writeSourceFile( final File file, final String content ) throws IOException
1359            {
1360                if ( file == null )
1361                {
1362                    throw new NullPointerException( "file" );
1363                }
1364                if ( content == null )
1365                {
1366                    throw new NullPointerException( "content" );
1367                }
1368    
1369                RandomAccessFile randomAccessFile = null;
1370                FileChannel fileChannel = null;
1371                FileLock fileLock = null;
1372                boolean suppressExceptionOnClose = true;
1373                final byte[] bytes = content.getBytes( getOutputEncoding() );
1374    
1375                try
1376                {
1377                    randomAccessFile = new RandomAccessFile( file, "rw" );
1378                    fileChannel = randomAccessFile.getChannel();
1379                    fileLock = fileChannel.lock( 0L, bytes.length, false );
1380                    fileChannel.truncate( bytes.length );
1381                    fileChannel.position( 0L );
1382                    fileChannel.write( ByteBuffer.wrap( bytes ) );
1383                    fileChannel.force( true );
1384                    suppressExceptionOnClose = false;
1385                }
1386                finally
1387                {
1388                    try
1389                    {
1390                        if ( fileLock != null )
1391                        {
1392                            fileLock.release();
1393                        }
1394                    }
1395                    catch ( final IOException e )
1396                    {
1397                        if ( suppressExceptionOnClose )
1398                        {
1399                            log( Level.SEVERE, null, e );
1400                        }
1401                        else
1402                        {
1403                            throw e;
1404                        }
1405                    }
1406                    finally
1407                    {
1408                        try
1409                        {
1410                            if ( fileChannel != null )
1411                            {
1412                                fileChannel.close();
1413                            }
1414                        }
1415                        catch ( final IOException e )
1416                        {
1417                            if ( suppressExceptionOnClose )
1418                            {
1419                                log( Level.SEVERE, null, e );
1420                            }
1421                            else
1422                            {
1423                                throw e;
1424                            }
1425                        }
1426                        finally
1427                        {
1428                            try
1429                            {
1430                                if ( randomAccessFile != null )
1431                                {
1432                                    randomAccessFile.close();
1433                                }
1434                            }
1435                            catch ( final IOException e )
1436                            {
1437                                if ( suppressExceptionOnClose )
1438                                {
1439                                    log( Level.SEVERE, null, e );
1440                                }
1441                                else
1442                                {
1443                                    throw e;
1444                                }
1445                            }
1446                        }
1447                    }
1448                }
1449            }
1450    
1451        }
1452    
1453    }