View Javadoc

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