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