001 /*
002 * Copyright (c) 2009 The JOMC Project
003 * Copyright (c) 2005 Christian Schulte <schulte2005@users.sourceforge.net>
004 * All rights reserved.
005 *
006 * Redistribution and use in source and binary forms, with or without
007 * modification, are permitted provided that the following conditions
008 * are met:
009 *
010 * o Redistributions of source code must retain the above copyright
011 * notice, this list of conditions and the following disclaimer.
012 *
013 * o Redistributions in binary form must reproduce the above copyright
014 * notice, this list of conditions and the following disclaimer in
015 * the documentation and/or other materials provided with the
016 * distribution.
017 *
018 * THIS SOFTWARE IS PROVIDED BY THE JOMC PROJECT AND CONTRIBUTORS "AS IS"
019 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE JOMC PROJECT OR
022 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
027 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
028 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 *
030 * $Id: SourceFileProcessor.java 1562 2010-03-07 01:06:40Z schulte2005 $
031 *
032 */
033 package org.jomc.tools;
034
035 import java.io.File;
036 import java.io.IOException;
037 import java.io.StringWriter;
038 import java.text.MessageFormat;
039 import java.util.Arrays;
040 import java.util.LinkedList;
041 import java.util.List;
042 import java.util.ResourceBundle;
043 import java.util.logging.Level;
044 import org.apache.commons.io.FileUtils;
045 import org.apache.velocity.Template;
046 import org.apache.velocity.VelocityContext;
047 import org.jomc.model.Dependencies;
048 import org.jomc.model.Implementation;
049 import org.jomc.model.Messages;
050 import org.jomc.model.Module;
051 import org.jomc.model.Properties;
052 import org.jomc.model.Specification;
053 import org.jomc.model.Specifications;
054 import org.jomc.tools.model.SourceFileType;
055 import org.jomc.tools.model.SourceFilesType;
056 import org.jomc.tools.model.SourceSectionType;
057 import org.jomc.tools.model.SourceSectionsType;
058 import org.jomc.util.LineEditor;
059 import org.jomc.util.Section;
060 import org.jomc.util.SectionEditor;
061 import org.jomc.util.TrailingWhitespaceEditor;
062
063 /**
064 * Processes source code files.
065 *
066 * <p><b>Use cases</b><br/><ul>
067 * <li>{@link #manageSourceFiles(File) }</li>
068 * <li>{@link #manageSourceFiles(Module, File) }</li>
069 * <li>{@link #manageSourceFiles(Specification, File) }</li>
070 * <li>{@link #manageSourceFiles(Implementation, File) }</li>
071 * </ul></p>
072 *
073 * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a>
074 * @version $Id: SourceFileProcessor.java 1562 2010-03-07 01:06:40Z schulte2005 $
075 */
076 public class SourceFileProcessor extends JomcTool
077 {
078
079 /** Constant for the name of the constructors source code section. */
080 private static final String CONSTRUCTORS_SECTION_NAME = "Constructors";
081
082 /** Constant for the name of the default constructor source code section. */
083 private static final String DEFAULT_CONSTRUCTOR_SECTION_NAME = "Default Constructor";
084
085 /** Constant for the name of the dependencies source code section. */
086 private static final String DEPENDENCIES_SECTION_NAME = "Dependencies";
087
088 /** Constant for the name of the properties source code section. */
089 private static final String PROPERTIES_SECTION_NAME = "Properties";
090
091 /** Constant for the name of the messages source code section. */
092 private static final String MESSAGES_SECTION_NAME = "Messages";
093
094 /** Constant for the name of the license source code section. */
095 private static final String LICENSE_SECTION_NAME = "License Header";
096
097 /** Constant for the name of the documentation source code section. */
098 private static final String DOCUMENTATION_SECTION_NAME = "Documentation";
099
100 /** Constant for the name of the implementation annotations source code section. */
101 private static final String ANNOTATIONS_SECTION_NAME = "Annotations";
102
103 /** Name of the generator. */
104 private static final String GENERATOR_NAME = SourceFileProcessor.class.getName();
105
106 /** Constant for the version of the generator. */
107 private static final String GENERATOR_VERSION = "1.0";
108
109 /** Name of the {@code implementation-constructors-head.vm} template. */
110 private static final String CONSTRUCTORS_HEAD_TEMPLATE = "implementation-constructors-head.vm";
111
112 /** Name of the {@code implementation-constructors-tail.vm} template. */
113 private static final String CONSTRUCTORS_TAIL_TEMPLATE = "implementation-constructors-tail.vm";
114
115 /** Name of the {@code implementation-default-constructor.vm} template. */
116 private static final String DEFAULT_CONSTRUCTOR_TEMPLATE = "implementation-default-constructor.vm";
117
118 /** Name of the {@code implementation-dependencies.vm} template. */
119 private static final String DEPENDENCIES_TEMPLATE = "implementation-dependencies.vm";
120
121 /** Name of the {@code implementation-properties.vm} template. */
122 private static final String PROPERTIES_TEMPLATE = "implementation-properties.vm";
123
124 /** Name of the {@code implementation-messages.vm} template. */
125 private static final String MESSAGES_TEMPLATE = "implementation-messages.vm";
126
127 /** Name of the {@code specification-license.vm} template. */
128 private static final String SPECIFICATION_LICENSE_TEMPLATE = "specification-license.vm";
129
130 /** Name of the {@code implementation-license.vm} template. */
131 private static final String IMPLEMENTATION_LICENSE_TEMPLATE = "implementation-license.vm";
132
133 /** Name of the {@code specification-documentation.vm} template. */
134 private static final String SPECIFICATION_DOCUMENTATION_TEMPLATE = "specification-documentation.vm";
135
136 /** Name of the {@code implementation-documentation.vm} template. */
137 private static final String IMPLEMENTATION_DOCUMENTATION_TEMPLATE = "implementation-documentation.vm";
138
139 /** Name of the {@code Implementation.java.vm} template. */
140 private static final String IMPLEMENTATION_TEMPLATE = "Implementation.java.vm";
141
142 /** Name of the {@code Specification.java.vm} template. */
143 private static final String SPECIFICATION_TEMPLATE = "Specification.java.vm";
144
145 /** Name of the {@code specification-annotations.vm} template. */
146 private static final String SPECIFICATION_ANNOTATIONS_TEMPLATE = "specification-annotations.vm";
147
148 /** Name of the {@code implementation-annotations.vm} template. */
149 private static final String IMPLEMENTATION_ANNOTATIONS_TEMPLATE = "implementation-annotations.vm";
150
151 /** Number of whitespace characters per indentation level. */
152 private Integer whitespacesPerIndent;
153
154 /** Indentation character. */
155 private Character indentationCharacter;
156
157 /** Source files model. */
158 private SourceFilesType sourceFilesType;
159
160 /** Creates a new {@code SourceFileProcessor} instance. */
161 public SourceFileProcessor()
162 {
163 super();
164 }
165
166 /**
167 * Creates a new {@code SourceFileProcessor} instance taking a {@code SourceFileProcessor} instance to initialize
168 * the instance with.
169 *
170 * @param tool The instance to initialize the new instance with,
171 *
172 * @throws NullPointerException if {@code tool} is {@code null}.
173 * @throws IOException if copying {@code tool} fails.
174 */
175 public SourceFileProcessor( final SourceFileProcessor tool ) throws IOException
176 {
177 super( tool );
178 this.setIndentationCharacter( tool.getIndentationCharacter() );
179 this.setWhitespacesPerIndent( tool.getWhitespacesPerIndent() );
180 this.sourceFilesType = new SourceFilesType( tool.getSourceFilesType() );
181 }
182
183 /**
184 * Gets the number of whitespace characters per indentation level.
185 *
186 * @return The number of whitespace characters per indentation level.
187 */
188 public int getWhitespacesPerIndent()
189 {
190 if ( this.whitespacesPerIndent == null )
191 {
192 this.whitespacesPerIndent = 4;
193 }
194
195 return this.whitespacesPerIndent;
196 }
197
198 /**
199 * Sets the number of whitespace characters per indentation level.
200 *
201 * @param value The new number of whitespace characters per indentation level.
202 */
203 public void setWhitespacesPerIndent( final int value )
204 {
205 this.whitespacesPerIndent = value;
206 }
207
208 /**
209 * Gets the indentation character.
210 *
211 * @return The indentation character.
212 */
213 public char getIndentationCharacter()
214 {
215 if ( this.indentationCharacter == null )
216 {
217 this.indentationCharacter = ' ';
218 }
219
220 return this.indentationCharacter;
221 }
222
223 /**
224 * Sets the indentation character.
225 *
226 * @param value The new indentation character.
227 */
228 public void setIndentationCharacter( final char value )
229 {
230 this.indentationCharacter = value;
231 }
232
233 /**
234 * Gets the source files model of the instance.
235 * <p>This accessor method returns a reference to the live object, not a snapshot. Therefore any modification you
236 * make to the returned object will be present inside the object. This is why there is no {@code set} method.</p>
237 *
238 * @return The source files model of the instance.
239 *
240 * @see #getSourceFileType(org.jomc.model.Specification)
241 * @see #getSourceFileType(org.jomc.model.Implementation)
242 */
243 public SourceFilesType getSourceFilesType()
244 {
245 if ( this.sourceFilesType == null )
246 {
247 this.sourceFilesType = new SourceFilesType();
248 }
249
250 return this.sourceFilesType;
251 }
252
253 /**
254 * Gets the model of a specification source file.
255 *
256 * @param specification The specification to get a source file model for.
257 *
258 * @return The source file model for {@code specification}.
259 *
260 * @throws NullPointerException if {@code specification} is {@code null}.
261 *
262 * @see #getSourceFilesType()
263 */
264 public SourceFileType getSourceFileType( final Specification specification )
265 {
266 if ( specification == null )
267 {
268 throw new NullPointerException( "specification" );
269 }
270
271 SourceFileType sourceFileType = this.getSourceFilesType().getSourceFile( specification.getIdentifier() );
272
273 if ( sourceFileType == null )
274 {
275 sourceFileType = specification.getAnyObject( SourceFileType.class );
276 }
277
278 if ( sourceFileType == null )
279 {
280 sourceFileType = new SourceFileType();
281 sourceFileType.setIdentifier( specification.getIdentifier() );
282 sourceFileType.setTemplate( SPECIFICATION_TEMPLATE );
283 sourceFileType.setSourceSections( new SourceSectionsType() );
284
285 SourceSectionType s = new SourceSectionType();
286 s.setName( LICENSE_SECTION_NAME );
287 s.setHeadTemplate( SPECIFICATION_LICENSE_TEMPLATE );
288 s.setOptional( true );
289 sourceFileType.getSourceSections().getSourceSection().add( s );
290
291 s = new SourceSectionType();
292 s.setName( ANNOTATIONS_SECTION_NAME );
293 s.setHeadTemplate( SPECIFICATION_ANNOTATIONS_TEMPLATE );
294 s.setOptional( false );
295 sourceFileType.getSourceSections().getSourceSection().add( s );
296
297 s = new SourceSectionType();
298 s.setName( DOCUMENTATION_SECTION_NAME );
299 s.setHeadTemplate( SPECIFICATION_DOCUMENTATION_TEMPLATE );
300 s.setOptional( true );
301 sourceFileType.getSourceSections().getSourceSection().add( s );
302
303 final String javaTypeName = this.getJavaTypeName( specification, false );
304 if ( javaTypeName != null )
305 {
306 s = new SourceSectionType();
307 s.setName( javaTypeName );
308 s.setIndentationLevel( 1 );
309 s.setOptional( false );
310 s.setEditable( true );
311 sourceFileType.getSourceSections().getSourceSection().add( s );
312 }
313 }
314
315 return sourceFileType;
316 }
317
318 /**
319 * Gets the model of an implementation source file.
320 *
321 * @param implementation The implementation to get a source file model for.
322 *
323 * @return The source file model for {@code implementation}.
324 *
325 * @throws NullPointerException if {@code implementation} is {@code null}.
326 *
327 * @see #getSourceFilesType()
328 */
329 public SourceFileType getSourceFileType( final Implementation implementation )
330 {
331 if ( implementation == null )
332 {
333 throw new NullPointerException( "implementation" );
334 }
335
336 SourceFileType sourceFileType = this.getSourceFilesType().getSourceFile( implementation.getIdentifier() );
337
338 if ( sourceFileType == null )
339 {
340 sourceFileType = implementation.getAnyObject( SourceFileType.class );
341 }
342
343 if ( sourceFileType == null )
344 {
345 final Specifications specifications = this.getModules().getSpecifications( implementation.getIdentifier() );
346 final Dependencies dependencies = this.getModules().getDependencies( implementation.getIdentifier() );
347 final Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
348 final Properties properties = this.getModules().getProperties( implementation.getIdentifier() );
349
350 sourceFileType = new SourceFileType();
351 sourceFileType.setIdentifier( implementation.getIdentifier() );
352 sourceFileType.setTemplate( IMPLEMENTATION_TEMPLATE );
353 sourceFileType.setSourceSections( new SourceSectionsType() );
354
355 SourceSectionType s = new SourceSectionType();
356 s.setName( LICENSE_SECTION_NAME );
357 s.setHeadTemplate( IMPLEMENTATION_LICENSE_TEMPLATE );
358 s.setOptional( true );
359 sourceFileType.getSourceSections().getSourceSection().add( s );
360
361 s = new SourceSectionType();
362 s.setName( ANNOTATIONS_SECTION_NAME );
363 s.setHeadTemplate( IMPLEMENTATION_ANNOTATIONS_TEMPLATE );
364 s.setOptional( false );
365 sourceFileType.getSourceSections().getSourceSection().add( s );
366
367 s = new SourceSectionType();
368 s.setName( DOCUMENTATION_SECTION_NAME );
369 s.setHeadTemplate( IMPLEMENTATION_DOCUMENTATION_TEMPLATE );
370 s.setOptional( true );
371 sourceFileType.getSourceSections().getSourceSection().add( s );
372
373 for ( String interfaceName : this.getJavaInterfaceNames( implementation, false ) )
374 {
375 s = new SourceSectionType();
376 s.setName( interfaceName );
377 s.setIndentationLevel( 1 );
378 s.setOptional( false );
379 s.setEditable( true );
380 sourceFileType.getSourceSections().getSourceSection().add( s );
381 }
382
383 s = new SourceSectionType();
384 s.setName( this.getJavaTypeName( implementation, false ) );
385 s.setIndentationLevel( 1 );
386 s.setOptional( false );
387 s.setEditable( true );
388 sourceFileType.getSourceSections().getSourceSection().add( s );
389
390 s = new SourceSectionType();
391 s.setName( CONSTRUCTORS_SECTION_NAME );
392 s.setIndentationLevel( 1 );
393 s.setHeadTemplate( CONSTRUCTORS_HEAD_TEMPLATE );
394 s.setTailTemplate( CONSTRUCTORS_TAIL_TEMPLATE );
395 s.setOptional( specifications == null ||
396 ( specifications.getSpecification().isEmpty() && specifications.getReference().isEmpty() ) );
397
398 s.setSourceSections( new SourceSectionsType() );
399 sourceFileType.getSourceSections().getSourceSection().add( s );
400
401 final SourceSectionType defaultCtor = new SourceSectionType();
402 defaultCtor.setName( DEFAULT_CONSTRUCTOR_SECTION_NAME );
403 defaultCtor.setIndentationLevel( 2 );
404 defaultCtor.setHeadTemplate( DEFAULT_CONSTRUCTOR_TEMPLATE );
405 defaultCtor.setOptional( false );
406 defaultCtor.setEditable( true );
407 s.getSourceSections().getSourceSection().add( defaultCtor );
408
409 s = new SourceSectionType();
410 s.setName( DEPENDENCIES_SECTION_NAME );
411 s.setIndentationLevel( 1 );
412 s.setHeadTemplate( DEPENDENCIES_TEMPLATE );
413 s.setOptional( dependencies == null || dependencies.getDependency().isEmpty() );
414 sourceFileType.getSourceSections().getSourceSection().add( s );
415
416 s = new SourceSectionType();
417 s.setName( PROPERTIES_SECTION_NAME );
418 s.setIndentationLevel( 1 );
419 s.setHeadTemplate( PROPERTIES_TEMPLATE );
420 s.setOptional( properties == null || properties.getProperty().isEmpty() );
421 sourceFileType.getSourceSections().getSourceSection().add( s );
422
423 s = new SourceSectionType();
424 s.setName( MESSAGES_SECTION_NAME );
425 s.setIndentationLevel( 1 );
426 s.setHeadTemplate( MESSAGES_TEMPLATE );
427 s.setOptional( messages == null || messages.getMessage().isEmpty() );
428 sourceFileType.getSourceSections().getSourceSection().add( s );
429 }
430
431 return sourceFileType;
432 }
433
434 /**
435 * Manages the source files of the modules of the instance.
436 *
437 * @param sourcesDirectory The directory holding the source files to manage.
438 *
439 * @throws NullPointerException if {@code sourcesDirectory} is {@code null}.
440 * @throws IOException if managing source files fails.
441 *
442 * @see #manageSourceFiles(org.jomc.model.Module, java.io.File)
443 */
444 public void manageSourceFiles( final File sourcesDirectory ) throws IOException
445 {
446 if ( sourcesDirectory == null )
447 {
448 throw new NullPointerException( "sourcesDirectory" );
449 }
450
451 for ( Module m : this.getModules().getModule() )
452 {
453 this.manageSourceFiles( m, sourcesDirectory );
454 }
455 }
456
457 /**
458 * Manages the source files of a given module of the modules of the instance.
459 *
460 * @param module The module to process.
461 * @param sourcesDirectory The directory holding the source files to manage.
462 *
463 * @throws NullPointerException if {@code module} or {@code sourcesDirectory} is {@code null}.
464 * @throws IOException if managing source files fails.
465 *
466 * @see #manageSourceFiles(org.jomc.model.Specification, java.io.File)
467 * @see #manageSourceFiles(org.jomc.model.Implementation, java.io.File)
468 */
469 public void manageSourceFiles( final Module module, final File sourcesDirectory ) throws IOException
470 {
471 if ( module == null )
472 {
473 throw new NullPointerException( "module" );
474 }
475 if ( sourcesDirectory == null )
476 {
477 throw new NullPointerException( "sourcesDirectory" );
478 }
479
480 if ( module.getSpecifications() != null )
481 {
482 for ( Specification s : module.getSpecifications().getSpecification() )
483 {
484 this.manageSourceFiles( s, sourcesDirectory );
485 }
486 }
487 if ( module.getImplementations() != null )
488 {
489 for ( Implementation i : module.getImplementations().getImplementation() )
490 {
491 this.manageSourceFiles( i, sourcesDirectory );
492 }
493 }
494 }
495
496 /**
497 * Manages the source file of a given specification of the modules of the instance.
498 *
499 * @param specification The specification to process.
500 * @param sourcesDirectory The directory holding the source files to manage.
501 *
502 * @throws NullPointerException if {@code specification} or {@code sourcesDirectory} is {@code null}.
503 * @throws IOException if managing source files fails.
504 *
505 * @see #getSourceFileEditor(org.jomc.model.Specification)
506 */
507 public void manageSourceFiles( final Specification specification, final File sourcesDirectory ) throws IOException
508 {
509 if ( specification == null )
510 {
511 throw new NullPointerException( "specification" );
512 }
513 if ( sourcesDirectory == null )
514 {
515 throw new NullPointerException( "sourcesDirectory" );
516 }
517
518 final Implementation i = this.getModules().getImplementation( specification.getIdentifier() );
519
520 if ( i != null && i.isClassDeclaration() )
521 {
522 this.manageSourceFiles( i, sourcesDirectory );
523 }
524 else if ( specification.isClassDeclaration() )
525 {
526 final File sourceFile =
527 new File( sourcesDirectory, specification.getClazz().replace( '.', '/' ) + ".java" );
528
529 this.editSourceFile( sourceFile, this.getSourceFileEditor( specification ) );
530 }
531 }
532
533 /**
534 * Manages the source file of a given implementation of the modules of the instance.
535 *
536 * @param implementation The implementation to process.
537 * @param sourcesDirectory The directory holding the source files to manage.
538 *
539 * @throws NullPointerException if {@code implementation} or {@code sourcesDirectory} is {@code null}.
540 * @throws IOException if managing source files fails.
541 *
542 * @see #getSourceFileEditor(org.jomc.model.Implementation)
543 */
544 public void manageSourceFiles( final Implementation implementation, final File sourcesDirectory )
545 throws IOException
546 {
547 if ( implementation == null )
548 {
549 throw new NullPointerException( "implementation" );
550 }
551 if ( sourcesDirectory == null )
552 {
553 throw new NullPointerException( "sourcesDirectory" );
554 }
555
556 if ( implementation.isClassDeclaration() )
557 {
558 final File sourceFile =
559 new File( sourcesDirectory, implementation.getClazz().replace( '.', '/' ) + ".java" );
560
561 this.editSourceFile( sourceFile, this.getSourceFileEditor( implementation ) );
562 }
563 }
564
565 /**
566 * Gets a new editor for editing the source file of a given specification.
567 *
568 * @param specification The specification whose source file to edit.
569 *
570 * @return A new editor for editing the source file of {@code specification}.
571 *
572 * @throws NullPointerException if {@code specification} is {@code null}.
573 */
574 public SourceFileEditor getSourceFileEditor( final Specification specification )
575 {
576 if ( specification == null )
577 {
578 throw new NullPointerException( "specification" );
579 }
580
581 return new SourceFileEditor( specification, new TrailingWhitespaceEditor() );
582 }
583
584 /**
585 * Gets a new editor for editing the source file of a given implementation.
586 *
587 * @param implementation The implementation whose source file to edit.
588 *
589 * @return A new editor for editing the source file of {@code implementation}.
590 *
591 * @throws NullPointerException if {@code implementation} is {@code null}.
592 */
593 public SourceFileEditor getSourceFileEditor( final Implementation implementation )
594 {
595 if ( implementation == null )
596 {
597 throw new NullPointerException( "implementation" );
598 }
599
600 return new SourceFileEditor( implementation, new TrailingWhitespaceEditor() );
601 }
602
603 /**
604 * Gets the velocity context used for merging templates.
605 *
606 * @return The velocity context used for merging templates.
607 */
608 @Override
609 public VelocityContext getVelocityContext()
610 {
611 final VelocityContext ctx = super.getVelocityContext();
612 ctx.put( "generatorName", GENERATOR_NAME );
613 ctx.put( "generatorVersion", GENERATOR_VERSION );
614 return ctx;
615 }
616
617 /**
618 * Edits a given file using a given editor.
619 *
620 * @param f The file to edit.
621 * @param editor The editor to edit {@code f} with.
622 *
623 * @throws NullPointerException if {@code f} or {@code editor} is {@code null}.
624 * @throws IOException if editing fails.
625 */
626 private void editSourceFile( final File f, final SourceFileEditor editor ) throws IOException
627 {
628 if ( f == null )
629 {
630 throw new NullPointerException( "f" );
631 }
632 if ( editor == null )
633 {
634 throw new NullPointerException( "editor" );
635 }
636
637 String content = null;
638
639 if ( !f.exists() )
640 {
641 final SourceFileType sourceFileType = editor.getSourceFileType();
642
643 if ( sourceFileType != null && sourceFileType.getTemplate() != null )
644 {
645 final StringWriter writer = new StringWriter();
646 final Template template = this.getVelocityTemplate( sourceFileType.getTemplate() );
647 final VelocityContext ctx = editor.getVelocityContext();
648 ctx.put( "template", template );
649 template.merge( ctx, writer );
650 writer.close();
651 content = writer.toString();
652 }
653 }
654 else
655 {
656 content = FileUtils.readFileToString( f, this.getInputEncoding() );
657 }
658
659 if ( content != null )
660 {
661 String edited = null;
662
663 try
664 {
665 edited = editor.edit( content );
666 }
667 catch ( final IOException e )
668 {
669 throw (IOException) new IOException( getMessage(
670 "failedEditing", f.getCanonicalPath(), e.getMessage() ) ).initCause( e );
671
672 }
673
674 if ( this.isLoggable( Level.FINE ) )
675 {
676 for ( Section s : editor.getAddedSections() )
677 {
678 this.log( Level.FINE, getMessage( "addedSection", f.getCanonicalPath(), s.getName() ), null );
679 }
680 }
681
682 if ( this.isLoggable( Level.WARNING ) )
683 {
684 for ( Section s : editor.getUnknownSections() )
685 {
686 this.log( Level.WARNING, getMessage( "unknownSection", f.getCanonicalPath(), s.getName() ),
687 null );
688
689 }
690 }
691
692 if ( !edited.equals( content ) )
693 {
694 if ( !f.getParentFile().exists() && !f.getParentFile().mkdirs() )
695 {
696 throw new IOException( getMessage( "failedCreatingDirectory",
697 f.getParentFile().getAbsolutePath() ) );
698
699 }
700
701 if ( this.isLoggable( Level.INFO ) )
702 {
703 this.log( Level.INFO, getMessage( "editing", f.getCanonicalPath() ), null );
704 }
705
706 FileUtils.writeStringToFile( f, edited, this.getOutputEncoding() );
707 }
708 }
709 }
710
711 private static String getMessage( final String key, final Object... arguments )
712 {
713 if ( key == null )
714 {
715 throw new NullPointerException( "key" );
716 }
717
718 return MessageFormat.format( ResourceBundle.getBundle( SourceFileProcessor.class.getName().replace( '.', '/' ) ).
719 getString( key ), arguments );
720
721 }
722
723 /**
724 * Extension to {@code SectionEditor} adding support for editing source code files.
725 *
726 * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a>
727 * @version $Id: SourceFileProcessor.java 1562 2010-03-07 01:06:40Z schulte2005 $
728 */
729 public class SourceFileEditor extends SectionEditor
730 {
731
732 /** {@code Specification} of the instance or {@code null}. */
733 private final Specification specification;
734
735 /** {@code Implementation} of the instance or {@code null}. */
736 private final Implementation implementation;
737
738 /** List of sections added to the input. */
739 private List<Section> addedSections;
740
741 /** List of sections without corresponding model entry. */
742 private List<Section> unknownSections;
743
744 /**
745 * Creates a new {@code SourceFileEditor} taking a {@code Specification} to edit source code of.
746 *
747 * @param specification The specification to edit source code of.
748 */
749 public SourceFileEditor( final Specification specification )
750 {
751 super();
752 this.specification = specification;
753 this.implementation = null;
754 }
755
756 /**
757 * Creates a new {@code SourceFileEditor} taking a {@code Specification} to edit source code of and an editor to
758 * chain.
759 *
760 * @param specification The specification backing the editor.
761 * @param lineEditor The editor to chain.
762 */
763 public SourceFileEditor( final Specification specification, final LineEditor lineEditor )
764 {
765 super( lineEditor );
766 this.specification = specification;
767 this.implementation = null;
768 }
769
770 /**
771 * Creates a new {@code SourceFileEditor} taking an {@code Implementation} to edit source code of.
772 *
773 * @param implementation The implementation to edit source code of.
774 */
775 public SourceFileEditor( final Implementation implementation )
776 {
777 super();
778 this.implementation = implementation;
779 this.specification = null;
780 }
781
782 /**
783 * Creates a new {@code SourceFileEditor} taking an {@code Implementation} to edit source code of and an editor
784 * to chain.
785 *
786 * @param implementation The implementation to edit source code of.
787 * @param lineEditor The editor to chain.
788 */
789 public SourceFileEditor( final Implementation implementation, final LineEditor lineEditor )
790 {
791 super( lineEditor );
792 this.implementation = implementation;
793 this.specification = null;
794 }
795
796 /**
797 * Gets a list of sections added to the input.
798 *
799 * @return A list of sections added to the input.
800 */
801 public List<Section> getAddedSections()
802 {
803 if ( this.addedSections == null )
804 {
805 this.addedSections = new LinkedList<Section>();
806 }
807
808 return this.addedSections;
809 }
810
811 /**
812 * Gets a list of sections without corresponding model entry.
813 *
814 * @return A list of sections without corresponding model entry.
815 */
816 public List<Section> getUnknownSections()
817 {
818 if ( this.unknownSections == null )
819 {
820 this.unknownSections = new LinkedList<Section>();
821 }
822
823 return this.unknownSections;
824 }
825
826 /**
827 * Gets the model of the editor.
828 *
829 * @return The model of the editor.
830 */
831 protected SourceFileType getSourceFileType()
832 {
833 if ( this.specification != null )
834 {
835 return SourceFileProcessor.this.getSourceFileType( this.specification );
836 }
837
838 if ( this.implementation != null )
839 {
840 return SourceFileProcessor.this.getSourceFileType( this.implementation );
841 }
842
843 return null;
844 }
845
846 /**
847 * Gets the velocity context used for merging templates.
848 *
849 * @return The velocity context used for merging templates.
850 */
851 protected VelocityContext getVelocityContext()
852 {
853 final VelocityContext ctx = SourceFileProcessor.this.getVelocityContext();
854
855 if ( this.specification != null )
856 {
857 ctx.put( "specification", this.specification );
858 }
859
860 if ( this.implementation != null )
861 {
862 ctx.put( "implementation", this.implementation );
863 }
864
865 return ctx;
866 }
867
868 /**
869 * {@inheritDoc}
870 * <p>This method creates any sections declared in the model of the editor as returned by method
871 * {@code getSourceFileType} prior to rendering the output of the editor.</p>
872 *
873 * @param section The section to start rendering the editor's output with.
874 *
875 * @see #getSourceFileType()
876 */
877 @Override
878 protected String getOutput( final Section section ) throws IOException
879 {
880 this.getAddedSections().clear();
881 this.getUnknownSections().clear();
882
883 final SourceFileType sourceFileType = this.getSourceFileType();
884
885 if ( sourceFileType != null )
886 {
887 this.createSections( sourceFileType.getSourceSections(), section );
888 }
889
890 return super.getOutput( section );
891 }
892
893 /**
894 * {@inheritDoc}
895 * <p>This method searches the model of the editor for a section matching {@code s} and updates properties
896 * {@code headContent} and {@code tailContent} of {@code s} according to the templates declared in the model
897 * as returned by method {@code getSourceFileType}.</p>
898 *
899 * @param s The section to edit.
900 *
901 * @see #getSourceFileType()
902 */
903 @Override
904 protected void editSection( final Section s ) throws IOException
905 {
906 super.editSection( s );
907
908 final SourceFileType sourceFileType = this.getSourceFileType();
909
910 if ( s.getName() != null && sourceFileType != null && sourceFileType.getSourceSections() != null )
911 {
912 final SourceSectionType sourceSectionType =
913 sourceFileType.getSourceSections().getSourceSection( s.getName() );
914
915 if ( sourceSectionType != null )
916 {
917 if ( sourceSectionType.getHeadTemplate() != null &&
918 ( !sourceSectionType.isEditable() || s.getHeadContent().toString().trim().length() == 0 ) )
919 {
920 final StringWriter writer = new StringWriter();
921 final Template template = getVelocityTemplate( sourceSectionType.getHeadTemplate() );
922 final VelocityContext ctx = getVelocityContext();
923 ctx.put( "template", template );
924 template.merge( ctx, writer );
925 writer.close();
926 s.getHeadContent().setLength( 0 );
927 s.getHeadContent().append( writer.toString() );
928 }
929
930 if ( sourceSectionType.getTailTemplate() != null &&
931 ( !sourceSectionType.isEditable() || s.getTailContent().toString().trim().length() == 0 ) )
932 {
933 final StringWriter writer = new StringWriter();
934 final Template template = getVelocityTemplate( sourceSectionType.getTailTemplate() );
935 final VelocityContext ctx = getVelocityContext();
936 ctx.put( "template", template );
937 template.merge( ctx, writer );
938 writer.close();
939 s.getTailContent().setLength( 0 );
940 s.getTailContent().append( writer.toString() );
941 }
942 }
943 else
944 {
945 this.getUnknownSections().add( s );
946 }
947 }
948 }
949
950 private void createSections( final SourceSectionsType sourceSectionsType, final Section section )
951 {
952 if ( sourceSectionsType != null && section != null )
953 {
954 for ( SourceSectionType sourceSectionType : sourceSectionsType.getSourceSection() )
955 {
956 Section childSection = section.getSection( sourceSectionType.getName() );
957
958 if ( childSection == null && !sourceSectionType.isOptional() )
959 {
960 final char[] indent =
961 new char[ getWhitespacesPerIndent() * sourceSectionType.getIndentationLevel() ];
962
963 Arrays.fill( indent, getIndentationCharacter() );
964
965 childSection = new Section();
966 childSection.setName( sourceSectionType.getName() );
967 childSection.setStartingLine(
968 String.valueOf( indent ) + "// SECTION-START[" + sourceSectionType.getName() + "]" );
969
970 childSection.setEndingLine( String.valueOf( indent ) + "// SECTION-END" );
971 section.getSections().add( childSection );
972
973 this.getAddedSections().add( childSection );
974 }
975
976 this.createSections( sourceSectionType.getSourceSections(), childSection );
977 }
978 }
979 }
980
981 }
982
983 }