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