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 4265 2012-02-16 21:17:26Z 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 4265 2012-02-16 21:17:26Z 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 4265 2012-02-16 21:17:26Z 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            /** List of sections added to the input. */
575            @Deprecated
576            private List<Section> addedSections;
577    
578            /** List of sections without corresponding model entry. */
579            @Deprecated
580            private List<Section> unknownSections;
581    
582            /**
583             * Creates a new {@code SourceFileEditor} instance.
584             *
585             * @since 1.2
586             */
587            public SourceFileEditor()
588            {
589                this( (LineEditor) null, (String) null );
590            }
591    
592            /**
593             * Creates a new {@code SourceFileEditor} instance taking a string to use for separating lines.
594             *
595             * @param lineSeparator String to use for separating lines.
596             *
597             * @since 1.2
598             */
599            public SourceFileEditor( final String lineSeparator )
600            {
601                this( (LineEditor) null, lineSeparator );
602            }
603    
604            /**
605             * Creates a new {@code SourceFileEditor} instance taking an editor to chain.
606             *
607             * @param editor The editor to chain.
608             *
609             * @since 1.2
610             */
611            public SourceFileEditor( final LineEditor editor )
612            {
613                this( editor, null );
614            }
615    
616            /**
617             * Creates a new {@code SourceFileEditor} instance taking an editor to chain and a string to use for separating
618             * lines.
619             *
620             * @param editor The editor to chain.
621             * @param lineSeparator String to use for separating lines.
622             *
623             * @since 1.2
624             */
625            public SourceFileEditor( final LineEditor editor, final String lineSeparator )
626            {
627                super( editor, lineSeparator );
628            }
629    
630            /**
631             * Creates a new {@code SourceFileEditor} taking a {@code Specification} to edit source code of.
632             *
633             * @param specification The specification to edit source code of.
634             *
635             * @deprecated As of JOMC 1.2, please use method {@link #edit(org.jomc.model.Specification, org.jomc.tools.model.SourceFileType, java.io.File)}.
636             * This constructor will be removed in version 2.0.
637             */
638            @Deprecated
639            public SourceFileEditor( final Specification specification )
640            {
641                this( specification, null, null );
642            }
643    
644            /**
645             * Creates a new {@code SourceFileEditor} taking a {@code Specification} to edit source code of and a line
646             * separator.
647             *
648             * @param specification The specification to edit source code of.
649             * @param lineSeparator The line separator of the editor.
650             *
651             * @deprecated As of JOMC 1.2, please use method {@link #edit(org.jomc.model.Specification, org.jomc.tools.model.SourceFileType, java.io.File)}.
652             * This constructor will be removed in version 2.0.
653             */
654            @Deprecated
655            public SourceFileEditor( final Specification specification, final String lineSeparator )
656            {
657                this( specification, null, lineSeparator );
658            }
659    
660            /**
661             * Creates a new {@code SourceFileEditor} taking a {@code Specification} to edit source code of and an editor to
662             * chain.
663             *
664             * @param specification The specification backing the editor.
665             * @param lineEditor The editor to chain.
666             *
667             * @deprecated As of JOMC 1.2, please use method {@link #edit(org.jomc.model.Specification, org.jomc.tools.model.SourceFileType, java.io.File)}.
668             * This constructor will be removed in version 2.0.
669             */
670            @Deprecated
671            public SourceFileEditor( final Specification specification, final LineEditor lineEditor )
672            {
673                this( specification, lineEditor, null );
674            }
675    
676            /**
677             * Creates a new {@code SourceFileEditor} taking a {@code Specification} to edit source code of, an editor to
678             * chain and a line separator.
679             *
680             * @param specification The specification backing the editor.
681             * @param lineEditor The editor to chain.
682             * @param lineSeparator The line separator of the editor.
683             *
684             * @deprecated As of JOMC 1.2, please use method {@link #edit(org.jomc.model.Specification, org.jomc.tools.model.SourceFileType, java.io.File)}.
685             * This constructor will be removed in version 2.0.
686             */
687            @Deprecated
688            public SourceFileEditor( final Specification specification, final LineEditor lineEditor,
689                                     final String lineSeparator )
690            {
691                super( lineEditor, lineSeparator );
692                this.specification = specification;
693                this.implementation = null;
694    
695                assert getModules().getSpecification( specification.getIdentifier() ) != null :
696                    "Specification '" + specification.getIdentifier() + "' not found.";
697    
698            }
699    
700            /**
701             * Creates a new {@code SourceFileEditor} taking an {@code Implementation} to edit source code of.
702             *
703             * @param implementation The implementation to edit source code of.
704             *
705             * @deprecated As of JOMC 1.2, please use method {@link #edit(org.jomc.model.Implementation, org.jomc.tools.model.SourceFileType, java.io.File)}.
706             * This constructor will be removed in version 2.0.
707             */
708            @Deprecated
709            public SourceFileEditor( final Implementation implementation )
710            {
711                this( implementation, null, null );
712            }
713    
714            /**
715             * Creates a new {@code SourceFileEditor} taking an {@code Implementation} to edit source code of and a line
716             * separator.
717             *
718             * @param implementation The implementation to edit source code of.
719             * @param lineSeparator The line separator of the editor.
720             *
721             * @deprecated As of JOMC 1.2, please use method {@link #edit(org.jomc.model.Implementation, org.jomc.tools.model.SourceFileType, java.io.File)}.
722             * This constructor will be removed in version 2.0.
723             */
724            @Deprecated
725            public SourceFileEditor( final Implementation implementation, final String lineSeparator )
726            {
727                this( implementation, null, lineSeparator );
728            }
729    
730            /**
731             * Creates a new {@code SourceFileEditor} taking an {@code Implementation} to edit source code of and an editor
732             * to chain.
733             *
734             * @param implementation The implementation to edit source code of.
735             * @param lineEditor The editor to chain.
736             *
737             * @deprecated As of JOMC 1.2, please use method {@link #edit(org.jomc.model.Implementation, org.jomc.tools.model.SourceFileType, java.io.File)}.
738             * This constructor will be removed in version 2.0.
739             */
740            @Deprecated
741            public SourceFileEditor( final Implementation implementation, final LineEditor lineEditor )
742            {
743                this( implementation, lineEditor, null );
744            }
745    
746            /**
747             * Creates a new {@code SourceFileEditor} taking an {@code Implementation} to edit source code of, an editor
748             * to chain and a line separator.
749             *
750             * @param implementation The implementation to edit source code of.
751             * @param lineEditor The editor to chain.
752             * @param lineSeparator The line separator of the editor.
753             *
754             * @deprecated As of JOMC 1.2, please use method {@link #edit(org.jomc.model.Implementation, org.jomc.tools.model.SourceFileType, java.io.File)}.
755             * This constructor will be removed in version 2.0.
756             */
757            @Deprecated
758            public SourceFileEditor( final Implementation implementation, final LineEditor lineEditor,
759                                     final String lineSeparator )
760            {
761                super( lineEditor, lineSeparator );
762                this.implementation = implementation;
763                this.specification = null;
764    
765                assert getModules().getImplementation( implementation.getIdentifier() ) != null :
766                    "Implementation '" + implementation.getIdentifier() + "' not found.";
767    
768            }
769    
770            /**
771             * Edits a source file of a given specification.
772             *
773             * @param specification The specification to edit a source file of.
774             * @param sourceFileType The model of the source file to edit.
775             * @param sourcesDirectory The directory holding the source file to edit.
776             *
777             * @throws NullPointerException if {@code specification}, {@code sourceFileType} or {@code sourcesDirectory} is
778             * {@code null}.
779             * @throws IOException if editing fails.
780             *
781             * @since 1.2
782             */
783            public final void edit( final Specification specification, final SourceFileType sourceFileType,
784                                    final File sourcesDirectory ) throws IOException
785            {
786                if ( specification == null )
787                {
788                    throw new NullPointerException( "specification" );
789                }
790                if ( sourceFileType == null )
791                {
792                    throw new NullPointerException( "sourceFileType" );
793                }
794                if ( sourcesDirectory == null )
795                {
796                    throw new NullPointerException( "sourcesDirectory" );
797                }
798    
799                assert getModules().getSpecification( specification.getIdentifier() ) != null :
800                    "Specification '" + specification.getIdentifier() + "' not found.";
801    
802                this.specification = specification;
803                this.sourceFileType = sourceFileType;
804                this.editSourceFile( sourcesDirectory );
805            }
806    
807            /**
808             * Edits a source file of a given implementation.
809             *
810             * @param implementation The implementation to edit a source file of.
811             * @param sourceFileType The model of the source file to edit.
812             * @param sourcesDirectory The directory holding the source file to edit.
813             *
814             * @throws NullPointerException if {@code implementation}, {@code sourceFileType} or {@code sourcesDirectory} is
815             * {@code null}.
816             * @throws IOException if editing fails.
817             *
818             * @since 1.2
819             */
820            public final void edit( final Implementation implementation, final SourceFileType sourceFileType,
821                                    final File sourcesDirectory ) throws IOException
822            {
823                if ( implementation == null )
824                {
825                    throw new NullPointerException( "implementation" );
826                }
827                if ( sourceFileType == null )
828                {
829                    throw new NullPointerException( "sourceFileType" );
830                }
831                if ( sourcesDirectory == null )
832                {
833                    throw new NullPointerException( "sourcesDirectory" );
834                }
835    
836                assert getModules().getImplementation( implementation.getIdentifier() ) != null :
837                    "Implementation '" + implementation.getIdentifier() + "' not found.";
838    
839                this.implementation = implementation;
840                this.sourceFileType = sourceFileType;
841                this.editSourceFile( sourcesDirectory );
842            }
843    
844            /**
845             * Gets a list of sections added to the input.
846             * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you
847             * make to the returned list will be present inside the object. This is why there is no {@code set} method
848             * for the added sections property.</p>
849             *
850             * @return A list of sections added to the input.
851             *
852             * @deprecated As of JOMC 1.2, deprecated without replacement. This method will be removed in version 2.0.
853             */
854            @Deprecated
855            public List<Section> getAddedSections()
856            {
857                if ( this.addedSections == null )
858                {
859                    this.addedSections = new LinkedList<Section>();
860                }
861    
862                return this.addedSections;
863            }
864    
865            /**
866             * Gets a list of sections without corresponding model entry.
867             * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you
868             * make to the returned list will be present inside the object. This is why there is no {@code set} method
869             * for the unknown sections property.</p>
870             *
871             * @return A list of sections without corresponding model entry.
872             *
873             * @deprecated As of JOMC 1.2, deprecated without replacement. This method will be removed in version 2.0.
874             */
875            @Deprecated
876            public List<Section> getUnknownSections()
877            {
878                if ( this.unknownSections == null )
879                {
880                    this.unknownSections = new LinkedList<Section>();
881                }
882    
883                return this.unknownSections;
884            }
885    
886            /**
887             * Gets the currently edited source code file.
888             *
889             * @return The currently edited source code file.
890             *
891             * @deprecated As of JOMC 1.2, deprecated without replacement. This method will be removed in version 2.0.
892             */
893            @Deprecated
894            protected SourceFileType getSourceFileType()
895            {
896                if ( this.sourceFileType == null )
897                {
898                    if ( this.specification != null )
899                    {
900                        return SourceFileProcessor.this.getSourceFileType( this.specification );
901                    }
902    
903                    if ( this.implementation != null )
904                    {
905                        return SourceFileProcessor.this.getSourceFileType( this.implementation );
906                    }
907                }
908    
909                return this.sourceFileType;
910            }
911    
912            /**
913             * Gets a new velocity context used for merging templates.
914             *
915             * @return A new velocity context used for merging templates.
916             *
917             * @deprecated As of JOMC 1.2, deprecated without replacement. This method will be removed in version 2.0.
918             */
919            @Deprecated
920            protected VelocityContext getVelocityContext()
921            {
922                final VelocityContext ctx = SourceFileProcessor.this.getVelocityContext();
923    
924                if ( this.specification != null )
925                {
926                    ctx.put( "specification", this.specification );
927                }
928    
929                if ( this.implementation != null )
930                {
931                    ctx.put( "implementation", this.implementation );
932                }
933    
934                return ctx;
935            }
936    
937            /**
938             * {@inheritDoc}
939             * <p>This method creates any sections declared in the model of the source file as returned by method
940             * {@code getSourceFileType} prior to rendering the output of the editor.</p>
941             *
942             * @param section The section to start rendering the editor's output with.
943             *
944             * @see #createSection(java.lang.String, java.lang.String, org.jomc.tools.model.SourceSectionType)
945             */
946            @Override
947            protected String getOutput( final Section section ) throws IOException
948            {
949                this.getAddedSections().clear();
950                this.getUnknownSections().clear();
951    
952                final SourceFileType model = this.getSourceFileType();
953    
954                if ( model != null )
955                {
956                    this.createSections( model, model.getSourceSections(), section );
957                }
958    
959                return super.getOutput( section );
960            }
961    
962            /**
963             * {@inheritDoc}
964             * <p>This method searches the model of the source file for a section matching {@code s} and updates properties
965             * {@code headContent} and {@code tailContent} of {@code s} according to the templates declared in the model
966             * as returned by method {@code getSourceFileType}.</p>
967             *
968             * @param s The section to edit.
969             */
970            @Override
971            protected void editSection( final Section s ) throws IOException
972            {
973                try
974                {
975                    super.editSection( s );
976    
977                    final SourceFileType model = this.getSourceFileType();
978    
979                    if ( s.getName() != null && model != null && model.getSourceSections() != null )
980                    {
981                        final SourceSectionType sourceSectionType =
982                            model.getSourceSections().getSourceSection( s.getName() );
983    
984                        if ( sourceSectionType != null )
985                        {
986                            if ( s.getStartingLine() != null )
987                            {
988                                s.setStartingLine( getIndentation( sourceSectionType.getIndentationLevel() )
989                                                   + s.getStartingLine().trim() );
990    
991                            }
992                            if ( s.getEndingLine() != null )
993                            {
994                                s.setEndingLine( getIndentation( sourceSectionType.getIndentationLevel() )
995                                                 + s.getEndingLine().trim() );
996    
997                            }
998    
999                            if ( sourceSectionType.getHeadTemplate() != null
1000                                 && ( !sourceSectionType.isEditable()
1001                                      || s.getHeadContent().toString().trim().length() == 0 ) )
1002                            {
1003                                final StringWriter writer = new StringWriter();
1004                                final Template template = getVelocityTemplate( sourceSectionType.getHeadTemplate() );
1005                                final VelocityContext ctx = getVelocityContext();
1006                                ctx.put( "template", template );
1007                                template.merge( ctx, writer );
1008                                writer.close();
1009                                s.getHeadContent().setLength( 0 );
1010                                s.getHeadContent().append( writer.toString() );
1011                            }
1012    
1013                            if ( sourceSectionType.getTailTemplate() != null
1014                                 && ( !sourceSectionType.isEditable()
1015                                      || s.getTailContent().toString().trim().length() == 0 ) )
1016                            {
1017                                final StringWriter writer = new StringWriter();
1018                                final Template template = getVelocityTemplate( sourceSectionType.getTailTemplate() );
1019                                final VelocityContext ctx = getVelocityContext();
1020                                ctx.put( "template", template );
1021                                template.merge( ctx, writer );
1022                                writer.close();
1023                                s.getTailContent().setLength( 0 );
1024                                s.getTailContent().append( writer.toString() );
1025                            }
1026                        }
1027                        else
1028                        {
1029                            if ( isLoggable( Level.WARNING ) )
1030                            {
1031                                if ( this.implementation != null )
1032                                {
1033                                    log( Level.WARNING, getMessage(
1034                                        "unknownImplementationSection", this.implementation.getIdentifier(),
1035                                        model.getIdentifier(), s.getName() ), null );
1036    
1037    
1038                                }
1039                                else if ( this.specification != null )
1040                                {
1041                                    log( Level.WARNING, getMessage(
1042                                        "unknownSpecificationSection", this.specification.getIdentifier(),
1043                                        model.getIdentifier(), s.getName() ), null );
1044    
1045                                }
1046                            }
1047    
1048                            this.getUnknownSections().add( s );
1049                        }
1050                    }
1051                }
1052                catch ( final VelocityException e )
1053                {
1054                    // JDK: As of JDK 6, "new IOException( message, cause )".
1055                    throw (IOException) new IOException( getMessage( e ) ).initCause( e );
1056                }
1057            }
1058    
1059            private void createSections( final SourceFileType sourceFileType, final SourceSectionsType sourceSectionsType,
1060                                         final Section section ) throws IOException
1061            {
1062                if ( sourceSectionsType != null && section != null )
1063                {
1064                    for ( int i = 0, s0 = sourceSectionsType.getSourceSection().size(); i < s0; i++ )
1065                    {
1066                        final SourceSectionType sourceSectionType = sourceSectionsType.getSourceSection().get( i );
1067                        Section childSection = section.getSection( sourceSectionType.getName() );
1068    
1069                        if ( childSection == null && !sourceSectionType.isOptional() )
1070                        {
1071                            childSection = this.createSection( StringUtils.defaultString( sourceFileType.getHeadComment() ),
1072                                                               StringUtils.defaultString( sourceFileType.getTailComment() ),
1073                                                               sourceSectionType );
1074    
1075                            section.getSections().add( childSection );
1076    
1077                            if ( isLoggable( Level.FINE ) )
1078                            {
1079                                log( Level.FINE, getMessage(
1080                                    "addedSection", sourceFileType.getIdentifier(), childSection.getName() ), null );
1081    
1082                            }
1083    
1084                            this.getAddedSections().add( childSection );
1085                        }
1086    
1087                        this.createSections( sourceFileType, sourceSectionType.getSourceSections(), childSection );
1088                    }
1089                }
1090            }
1091    
1092            /**
1093             * Creates a new {@code Section} instance for a given {@code SourceSectionType}.
1094             *
1095             * @param headComment Characters to use to start a comment in the source file.
1096             * @param tailComment Characters to use to end a comment in the source file.
1097             * @param sourceSectionType The {@code SourceSectionType} to create a new {@code Section} instance for.
1098             *
1099             * @return A new {@code Section} instance for {@code sourceSectionType}.
1100             *
1101             * @throws NullPointerException if {@code headComment}, {@code tailComment} or {@code sourceSectionType} is
1102             * {@code null}.
1103             * @throws IOException if creating a new {@code Section} instance fails.
1104             *
1105             * @since 1.2
1106             */
1107            private Section createSection( final String headComment, final String tailComment,
1108                                           final SourceSectionType sourceSectionType ) throws IOException
1109            {
1110                if ( headComment == null )
1111                {
1112                    throw new NullPointerException( "headComment" );
1113                }
1114                if ( tailComment == null )
1115                {
1116                    throw new NullPointerException( "tailComment" );
1117                }
1118                if ( sourceSectionType == null )
1119                {
1120                    throw new NullPointerException( "sourceSectionType" );
1121                }
1122    
1123                final Section s = new Section();
1124                s.setName( sourceSectionType.getName() );
1125    
1126                final StringBuilder head = new StringBuilder( 255 );
1127                head.append( getIndentation( sourceSectionType.getIndentationLevel() ) ).append( headComment );
1128    
1129                s.setStartingLine( head + " SECTION-START[" + sourceSectionType.getName() + ']' + tailComment );
1130                s.setEndingLine( head + " SECTION-END" + tailComment );
1131    
1132                return s;
1133            }
1134    
1135            private void editSourceFile( final File sourcesDirectory ) throws IOException
1136            {
1137                if ( sourcesDirectory == null )
1138                {
1139                    throw new NullPointerException( "sourcesDirectory" );
1140                }
1141                if ( !sourcesDirectory.isDirectory() )
1142                {
1143                    throw new IOException( getMessage( "directoryNotFound", sourcesDirectory.getAbsolutePath() ) );
1144                }
1145    
1146                final SourceFileType model = this.getSourceFileType();
1147    
1148                if ( model != null && model.getLocation() != null )
1149                {
1150                    final File f = new File( sourcesDirectory, model.getLocation() );
1151    
1152                    try
1153                    {
1154                        String content = "";
1155                        String edited = null;
1156                        boolean creating = false;
1157    
1158                        if ( !f.exists() )
1159                        {
1160                            if ( model.getTemplate() != null )
1161                            {
1162                                final StringWriter writer = new StringWriter();
1163                                final Template template = getVelocityTemplate( model.getTemplate() );
1164                                final VelocityContext ctx = this.getVelocityContext();
1165                                ctx.put( "template", template );
1166                                template.merge( ctx, writer );
1167                                writer.close();
1168                                content = writer.toString();
1169                                creating = true;
1170                            }
1171                        }
1172                        else
1173                        {
1174                            if ( isLoggable( Level.FINER ) )
1175                            {
1176                                log( Level.FINER, getMessage( "reading", f.getAbsolutePath() ), null );
1177                            }
1178    
1179                            content = this.readSourceFile( f );
1180                        }
1181    
1182                        try
1183                        {
1184                            edited = super.edit( content );
1185                        }
1186                        catch ( final IOException e )
1187                        {
1188                            // JDK: As of JDK 6, "new IOException( message, cause )".
1189                            throw (IOException) new IOException( getMessage(
1190                                "failedEditing", f.getAbsolutePath(), getMessage( e ) ) ).initCause( e );
1191    
1192                        }
1193    
1194                        if ( !edited.equals( content ) || edited.length() == 0 )
1195                        {
1196                            if ( !f.getParentFile().exists() && !f.getParentFile().mkdirs() )
1197                            {
1198                                throw new IOException( getMessage(
1199                                    "failedCreatingDirectory", f.getParentFile().getAbsolutePath() ) );
1200    
1201                            }
1202    
1203                            if ( isLoggable( Level.INFO ) )
1204                            {
1205                                log( Level.INFO, getMessage(
1206                                    creating ? "creating" : "editing", f.getAbsolutePath() ), null );
1207    
1208                            }
1209    
1210                            this.writeSourceFile( f, edited );
1211                        }
1212                        else if ( isLoggable( Level.FINER ) )
1213                        {
1214                            log( Level.FINER, getMessage( "unchanged", f.getAbsolutePath() ), null );
1215                        }
1216                    }
1217                    catch ( final VelocityException e )
1218                    {
1219                        // JDK: As of JDK 6, "new IOException( message, cause )".
1220                        throw (IOException) new IOException( getMessage(
1221                            "failedEditing", f.getAbsolutePath(), getMessage( e ) ) ).initCause( e );
1222    
1223                    }
1224                }
1225            }
1226    
1227            private String readSourceFile( final File file ) throws IOException
1228            {
1229                if ( file == null )
1230                {
1231                    throw new NullPointerException( "file" );
1232                }
1233    
1234                RandomAccessFile randomAccessFile = null;
1235                FileChannel fileChannel = null;
1236                FileLock fileLock = null;
1237                boolean suppressExceptionOnClose = true;
1238    
1239                //final Charset charset = Charset.forName( getInputEncoding() );
1240                final int length = file.length() > 0L ? Long.valueOf( file.length() ).intValue() : 1;
1241                final ByteBuffer buf = ByteBuffer.allocate( length );
1242                final StringBuilder appendable = new StringBuilder( length );
1243    
1244                try
1245                {
1246                    randomAccessFile = new RandomAccessFile( file, "r" );
1247                    fileChannel = randomAccessFile.getChannel();
1248                    fileLock = fileChannel.lock( 0L, file.length(), true );
1249                    fileChannel.position( 0L );
1250    
1251                    buf.clear();
1252                    int read = fileChannel.read( buf );
1253    
1254                    while ( read != -1 )
1255                    {
1256                        // JDK: As of JDK 6, new String( byte[], int, int, Charset )
1257                        appendable.append( new String( buf.array(), buf.arrayOffset(), read, getInputEncoding() ) );
1258                        buf.clear();
1259                        read = fileChannel.read( buf );
1260                    }
1261    
1262                    suppressExceptionOnClose = false;
1263                    return appendable.toString();
1264                }
1265                finally
1266                {
1267                    try
1268                    {
1269                        if ( fileLock != null )
1270                        {
1271                            fileLock.release();
1272                        }
1273                    }
1274                    catch ( final IOException e )
1275                    {
1276                        if ( suppressExceptionOnClose )
1277                        {
1278                            log( Level.SEVERE, null, e );
1279                        }
1280                        else
1281                        {
1282                            throw e;
1283                        }
1284                    }
1285                    finally
1286                    {
1287                        try
1288                        {
1289                            if ( fileChannel != null )
1290                            {
1291                                fileChannel.close();
1292                            }
1293                        }
1294                        catch ( final IOException e )
1295                        {
1296                            if ( suppressExceptionOnClose )
1297                            {
1298                                log( Level.SEVERE, null, e );
1299                            }
1300                            else
1301                            {
1302                                throw e;
1303                            }
1304                        }
1305                        finally
1306                        {
1307                            try
1308                            {
1309                                if ( randomAccessFile != null )
1310                                {
1311                                    randomAccessFile.close();
1312                                }
1313                            }
1314                            catch ( final IOException e )
1315                            {
1316                                if ( suppressExceptionOnClose )
1317                                {
1318                                    log( Level.SEVERE, null, e );
1319                                }
1320                                else
1321                                {
1322                                    throw e;
1323                                }
1324                            }
1325                        }
1326                    }
1327                }
1328            }
1329    
1330            private void writeSourceFile( final File file, final String content ) throws IOException
1331            {
1332                if ( file == null )
1333                {
1334                    throw new NullPointerException( "file" );
1335                }
1336                if ( content == null )
1337                {
1338                    throw new NullPointerException( "content" );
1339                }
1340    
1341                RandomAccessFile randomAccessFile = null;
1342                FileChannel fileChannel = null;
1343                FileLock fileLock = null;
1344                boolean suppressExceptionOnClose = true;
1345                final byte[] bytes = content.getBytes( getOutputEncoding() );
1346    
1347                try
1348                {
1349                    randomAccessFile = new RandomAccessFile( file, "rw" );
1350                    fileChannel = randomAccessFile.getChannel();
1351                    fileLock = fileChannel.lock( 0L, bytes.length, false );
1352                    fileChannel.truncate( bytes.length );
1353                    fileChannel.position( 0L );
1354                    fileChannel.write( ByteBuffer.wrap( bytes ) );
1355                    fileChannel.force( true );
1356                    suppressExceptionOnClose = false;
1357                }
1358                finally
1359                {
1360                    try
1361                    {
1362                        if ( fileLock != null )
1363                        {
1364                            fileLock.release();
1365                        }
1366                    }
1367                    catch ( final IOException e )
1368                    {
1369                        if ( suppressExceptionOnClose )
1370                        {
1371                            log( Level.SEVERE, null, e );
1372                        }
1373                        else
1374                        {
1375                            throw e;
1376                        }
1377                    }
1378                    finally
1379                    {
1380                        try
1381                        {
1382                            if ( fileChannel != null )
1383                            {
1384                                fileChannel.close();
1385                            }
1386                        }
1387                        catch ( final IOException e )
1388                        {
1389                            if ( suppressExceptionOnClose )
1390                            {
1391                                log( Level.SEVERE, null, e );
1392                            }
1393                            else
1394                            {
1395                                throw e;
1396                            }
1397                        }
1398                        finally
1399                        {
1400                            try
1401                            {
1402                                if ( randomAccessFile != null )
1403                                {
1404                                    randomAccessFile.close();
1405                                }
1406                            }
1407                            catch ( final IOException e )
1408                            {
1409                                if ( suppressExceptionOnClose )
1410                                {
1411                                    log( Level.SEVERE, null, e );
1412                                }
1413                                else
1414                                {
1415                                    throw e;
1416                                }
1417                            }
1418                        }
1419                    }
1420                }
1421            }
1422    
1423        }
1424    
1425    }