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: ResourceFileProcessor.java 4670 2012-12-23 01:20:36Z schulte $
29   *
30   */
31  package org.jomc.tools;
32  
33  import java.io.ByteArrayOutputStream;
34  import java.io.Closeable;
35  import java.io.File;
36  import java.io.IOException;
37  import java.io.RandomAccessFile;
38  import java.nio.ByteBuffer;
39  import java.nio.channels.FileChannel;
40  import java.nio.channels.FileLock;
41  import java.text.MessageFormat;
42  import java.util.HashMap;
43  import java.util.Locale;
44  import java.util.Map;
45  import java.util.Properties;
46  import java.util.ResourceBundle;
47  import java.util.logging.Level;
48  import org.apache.velocity.VelocityContext;
49  import org.jomc.model.Implementation;
50  import org.jomc.model.JavaTypeName;
51  import org.jomc.model.Message;
52  import org.jomc.model.Messages;
53  import org.jomc.model.ModelObjectException;
54  import org.jomc.model.Module;
55  import org.jomc.model.Specification;
56  import org.jomc.model.Text;
57  
58  /**
59   * Processes resource files.
60   *
61   * <p><b>Use Cases:</b><br/><ul>
62   * <li>{@link #writeResourceBundleResourceFiles(File) }</li>
63   * <li>{@link #writeResourceBundleResourceFiles(Module, File) }</li>
64   * <li>{@link #writeResourceBundleResourceFiles(Specification, File) }</li>
65   * <li>{@link #writeResourceBundleResourceFiles(Implementation, File) }</li>
66   * </ul></p>
67   *
68   * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
69   * @version $JOMC: ResourceFileProcessor.java 4670 2012-12-23 01:20:36Z schulte $
70   *
71   * @see #getModules()
72   */
73  public class ResourceFileProcessor extends JomcTool
74  {
75  
76      /** The language of the default language properties file of generated resource bundle resources. */
77      private Locale resourceBundleDefaultLocale;
78  
79      /** Creates a new {@code ResourceFileProcessor} instance. */
80      public ResourceFileProcessor()
81      {
82          super();
83      }
84  
85      /**
86       * Creates a new {@code ResourceFileProcessor} instance taking a {@code ResourceFileProcessor} instance to
87       * initialize the instance with.
88       *
89       * @param tool The instance to initialize the new instance with.
90       *
91       * @throws NullPointerException if {@code tool} is {@code null}.
92       * @throws IOException if copying {@code tool} fails.
93       */
94      public ResourceFileProcessor( final ResourceFileProcessor tool ) throws IOException
95      {
96          super( tool );
97          this.resourceBundleDefaultLocale = tool.resourceBundleDefaultLocale;
98      }
99  
100     /**
101      * Gets the language of the default language properties file of generated resource bundle resource files.
102      *
103      * @return The language of the default language properties file of generated resource bundle resource files.
104      *
105      * @see #setResourceBundleDefaultLocale(java.util.Locale)
106      */
107     public final Locale getResourceBundleDefaultLocale()
108     {
109         if ( this.resourceBundleDefaultLocale == null )
110         {
111             this.resourceBundleDefaultLocale = Locale.ENGLISH;
112 
113             if ( this.isLoggable( Level.CONFIG ) )
114             {
115                 this.log( Level.CONFIG, getMessage( "defaultResourceBundleDefaultLocale",
116                                                     this.resourceBundleDefaultLocale ), null );
117 
118             }
119         }
120 
121         return this.resourceBundleDefaultLocale;
122     }
123 
124     /**
125      * Sets the language of the default language properties file of generated resource bundle resource files.
126      *
127      * @param value The language of the default language properties file of generated resource bundle resource files.
128      *
129      * @see #getResourceBundleDefaultLocale()
130      */
131     public final void setResourceBundleDefaultLocale( final Locale value )
132     {
133         this.resourceBundleDefaultLocale = value;
134     }
135 
136     /**
137      * Writes resource bundle resource files of the modules of the instance to a given directory.
138      *
139      * @param resourcesDirectory The directory to write resource bundle resource files to.
140      *
141      * @throws NullPointerException if {@code resourcesDirectory} is {@code null}.
142      * @throws IOException if writing resource bundle resource files fails.
143      * @throws ModelObjectException if compiling the name of a referenced type fails.
144      *
145      * @see #writeResourceBundleResourceFiles(org.jomc.model.Module, java.io.File)
146      */
147     public void writeResourceBundleResourceFiles( final File resourcesDirectory )
148         throws IOException, ModelObjectException
149     {
150         if ( resourcesDirectory == null )
151         {
152             throw new NullPointerException( "resourcesDirectory" );
153         }
154 
155         if ( this.getModules() != null )
156         {
157             for ( int i = 0, s0 = this.getModules().getModule().size(); i < s0; i++ )
158             {
159                 this.writeResourceBundleResourceFiles( this.getModules().getModule().get( i ), resourcesDirectory );
160             }
161         }
162         else if ( this.isLoggable( Level.WARNING ) )
163         {
164             this.log( Level.WARNING, getMessage( "modulesNotFound", this.getModel().getIdentifier() ), null );
165         }
166     }
167 
168     /**
169      * Writes resource bundle resource files of a given module from the modules of the instance to a given directory.
170      *
171      * @param module The module to process.
172      * @param resourcesDirectory The directory to write resource bundle resource files to.
173      *
174      * @throws NullPointerException if {@code module} or {@code resourcesDirectory} is {@code null}.
175      * @throws IOException if writing resource bundle resource files fails.
176      * @throws ModelObjectException if compiling the name of a referenced type fails.
177      *
178      * @see #writeResourceBundleResourceFiles(org.jomc.model.Specification, java.io.File)
179      * @see #writeResourceBundleResourceFiles(org.jomc.model.Implementation, java.io.File)
180      */
181     public void writeResourceBundleResourceFiles( final Module module, final File resourcesDirectory )
182         throws IOException, ModelObjectException
183     {
184         if ( module == null )
185         {
186             throw new NullPointerException( "module" );
187         }
188         if ( resourcesDirectory == null )
189         {
190             throw new NullPointerException( "resourcesDirectory" );
191         }
192 
193         if ( this.getModules() != null && this.getModules().getModule( module.getName() ) != null )
194         {
195             if ( module.getSpecifications() != null )
196             {
197                 for ( int i = 0, s0 = module.getSpecifications().getSpecification().size(); i < s0; i++ )
198                 {
199                     this.writeResourceBundleResourceFiles( module.getSpecifications().getSpecification().get( i ),
200                                                            resourcesDirectory );
201 
202                 }
203             }
204 
205             if ( module.getImplementations() != null )
206             {
207                 for ( int i = 0, s0 = module.getImplementations().getImplementation().size(); i < s0; i++ )
208                 {
209                     this.writeResourceBundleResourceFiles( module.getImplementations().getImplementation().get( i ),
210                                                            resourcesDirectory );
211 
212                 }
213             }
214         }
215         else if ( this.isLoggable( Level.WARNING ) )
216         {
217             this.log( Level.WARNING, getMessage( "moduleNotFound", module.getName() ), null );
218         }
219     }
220 
221     /**
222      * Writes resource bundle resource files of a given specification from the modules of the instance to a directory.
223      *
224      * @param specification The specification to process.
225      * @param resourcesDirectory The directory to write resource bundle resource files to.
226      *
227      * @throws NullPointerException if {@code specification} or {@code resourcesDirectory} is {@code null}.
228      * @throws IOException if writing resource bundle resource files fails.
229      * @throws ModelObjectException if compiling the name of the type referenced by the specification fails.
230      *
231      * @see #getResourceBundleResources(org.jomc.model.Specification)
232      */
233     public void writeResourceBundleResourceFiles( final Specification specification, final File resourcesDirectory )
234         throws IOException, ModelObjectException
235     {
236         if ( specification == null )
237         {
238             throw new NullPointerException( "implementation" );
239         }
240         if ( resourcesDirectory == null )
241         {
242             throw new NullPointerException( "resourcesDirectory" );
243         }
244 
245         if ( this.getModules() != null
246              && this.getModules().getSpecification( specification.getIdentifier() ) != null )
247         {
248             if ( specification.isClassDeclaration() )
249             {
250                 if ( !resourcesDirectory.isDirectory() )
251                 {
252                     throw new IOException( getMessage( "directoryNotFound", resourcesDirectory.getAbsolutePath() ) );
253                 }
254 
255                 this.assertValidTemplates( specification );
256 
257                 final JavaTypeName javaTypeName = specification.getJavaTypeName();
258 
259                 if ( javaTypeName != null )
260                 {
261                     final String bundlePath = javaTypeName.getQualifiedName().replace( '.', File.separatorChar );
262                     this.writeResourceBundleResourceFiles(
263                         this.getResourceBundleResources( specification ), resourcesDirectory, bundlePath );
264 
265                 }
266             }
267         }
268         else if ( this.isLoggable( Level.WARNING ) )
269         {
270             this.log( Level.WARNING, getMessage( "specificationNotFound", specification.getIdentifier() ), null );
271         }
272     }
273 
274     /**
275      * Writes resource bundle resource files of a given implementation from the modules of the instance to a directory.
276      *
277      * @param implementation The implementation to process.
278      * @param resourcesDirectory The directory to write resource bundle resource files to.
279      *
280      * @throws NullPointerException if {@code implementation} or {@code resourcesDirectory} is {@code null}.
281      * @throws IOException if writing resource bundle resource files fails.
282      * @throws ModelObjectException if compiling the name of the type referenced by the implementation fails.
283      *
284      * @see #getResourceBundleResources(org.jomc.model.Implementation)
285      */
286     public void writeResourceBundleResourceFiles( final Implementation implementation, final File resourcesDirectory )
287         throws IOException, ModelObjectException
288     {
289         if ( implementation == null )
290         {
291             throw new NullPointerException( "implementation" );
292         }
293         if ( resourcesDirectory == null )
294         {
295             throw new NullPointerException( "resourcesDirectory" );
296         }
297 
298         if ( this.getModules() != null
299              && this.getModules().getImplementation( implementation.getIdentifier() ) != null )
300         {
301             if ( implementation.isClassDeclaration() )
302             {
303                 if ( !resourcesDirectory.isDirectory() )
304                 {
305                     throw new IOException( getMessage( "directoryNotFound", resourcesDirectory.getAbsolutePath() ) );
306                 }
307 
308                 this.assertValidTemplates( implementation );
309 
310                 final JavaTypeName javaTypeName = implementation.getJavaTypeName();
311 
312                 if ( javaTypeName != null )
313                 {
314                     final String bundlePath = javaTypeName.getQualifiedName().replace( '.', File.separatorChar );
315                     this.writeResourceBundleResourceFiles(
316                         this.getResourceBundleResources( implementation ), resourcesDirectory, bundlePath );
317 
318                 }
319             }
320         }
321         else if ( this.isLoggable( Level.WARNING ) )
322         {
323             this.log( Level.WARNING, getMessage( "implementationNotFound", implementation.getIdentifier() ), null );
324         }
325     }
326 
327     /**
328      * Gets resource bundle properties resources of a given specification.
329      *
330      * @param specification The specification to get resource bundle properties resources of.
331      *
332      * @return Resource bundle properties resources of {@code specification} or {@code null}, if no model objects are
333      * found.
334      *
335      * @throws NullPointerException if {@code specification} is {@code null}.
336      * @throws IOException if getting the resource bundle properties resources fails.
337      */
338     public Map<Locale, Properties> getResourceBundleResources( final Specification specification )
339         throws IOException
340     {
341         if ( specification == null )
342         {
343             throw new NullPointerException( "specification" );
344         }
345 
346         Map<Locale, Properties> properties = null;
347 
348         if ( this.getModules() != null
349              && this.getModules().getSpecification( specification.getIdentifier() ) != null )
350         {
351             properties = new HashMap<Locale, Properties>();
352         }
353         else if ( this.isLoggable( Level.WARNING ) )
354         {
355             this.log( Level.WARNING, getMessage( "specificationNotFound", specification.getIdentifier() ), null );
356         }
357 
358         return properties;
359     }
360 
361     /**
362      * Gets resource bundle properties resources of a given implementation.
363      *
364      * @param implementation The implementation to get resource bundle properties resources of.
365      *
366      * @return Resource bundle properties resources of {@code implementation} or {@code null}, if no model objects are
367      * found.
368      *
369      * @throws NullPointerException if {@code implementation} is {@code null}.
370      * @throws IOException if getting the resource bundle properties resources fails.
371      */
372     public Map<Locale, Properties> getResourceBundleResources( final Implementation implementation )
373         throws IOException
374     {
375         if ( implementation == null )
376         {
377             throw new NullPointerException( "implementation" );
378         }
379 
380         Map<Locale, Properties> properties = null;
381 
382         if ( this.getModules() != null
383              && this.getModules().getImplementation( implementation.getIdentifier() ) != null )
384         {
385             properties = new HashMap<Locale, java.util.Properties>( 10 );
386             final Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
387 
388             if ( messages != null )
389             {
390                 for ( int i = 0, s0 = messages.getMessage().size(); i < s0; i++ )
391                 {
392                     final Message message = messages.getMessage().get( i );
393 
394                     if ( message.getTemplate() != null )
395                     {
396                         for ( int j = 0, s1 = message.getTemplate().getText().size(); j < s1; j++ )
397                         {
398                             final Text text = message.getTemplate().getText().get( j );
399                             final Locale locale = new Locale( text.getLanguage().toLowerCase() );
400                             Properties bundleProperties = properties.get( locale );
401 
402                             if ( bundleProperties == null )
403                             {
404                                 bundleProperties = new Properties();
405                                 properties.put( locale, bundleProperties );
406                             }
407 
408                             bundleProperties.setProperty( message.getName(), text.getValue() );
409                         }
410                     }
411                 }
412             }
413         }
414         else if ( this.isLoggable( Level.WARNING ) )
415         {
416             this.log( Level.WARNING, getMessage( "implementationNotFound", implementation.getIdentifier() ), null );
417         }
418 
419         return properties;
420     }
421 
422     private void writeResourceBundleResourceFiles( final Map<Locale, Properties> resources,
423                                                    final File resourcesDirectory, final String bundlePath )
424         throws IOException
425     {
426         if ( resources == null )
427         {
428             throw new NullPointerException( "resources" );
429         }
430         if ( resourcesDirectory == null )
431         {
432             throw new NullPointerException( "resourcesDirectory" );
433         }
434         if ( bundlePath == null )
435         {
436             throw new NullPointerException( "bundlePath" );
437         }
438 
439         Properties defProperties = null;
440         Properties fallbackProperties = null;
441 
442         final VelocityContext ctx = this.getVelocityContext();
443         final String toolName = ctx.get( "toolName" ).toString();
444         final String toolVersion = ctx.get( "toolVersion" ).toString();
445         final String toolUrl = ctx.get( "toolUrl" ).toString();
446 
447         for ( Map.Entry<Locale, Properties> e : resources.entrySet() )
448         {
449             final String language = e.getKey().getLanguage().toLowerCase();
450             final Properties p = e.getValue();
451             final File file = new File( resourcesDirectory, bundlePath + "_" + language + ".properties" );
452 
453             if ( this.getResourceBundleDefaultLocale().getLanguage().equalsIgnoreCase( language ) )
454             {
455                 defProperties = p;
456             }
457 
458             fallbackProperties = p;
459 
460             if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() )
461             {
462                 throw new IOException( getMessage( "failedCreatingDirectory",
463                                                    file.getParentFile().getAbsolutePath() ) );
464 
465             }
466 
467             if ( this.isLoggable( Level.INFO ) )
468             {
469                 this.log( Level.INFO, getMessage( "writing", file.getCanonicalPath() ), null );
470             }
471 
472             this.writePropertiesFile( p, toolName + ' ' + toolVersion + " - See " + toolUrl, file );
473         }
474 
475         if ( defProperties == null )
476         {
477             defProperties = fallbackProperties;
478         }
479 
480         if ( defProperties != null )
481         {
482             final File file = new File( resourcesDirectory, bundlePath + ".properties" );
483 
484             if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() )
485             {
486                 throw new IOException( getMessage( "failedCreatingDirectory",
487                                                    file.getParentFile().getAbsolutePath() ) );
488 
489             }
490 
491             if ( this.isLoggable( Level.INFO ) )
492             {
493                 this.log( Level.INFO, getMessage( "writing", file.getCanonicalPath() ), null );
494             }
495 
496             this.writePropertiesFile( defProperties, toolName + ' ' + toolVersion + " - See " + toolUrl, file );
497         }
498     }
499 
500     private void assertValidTemplates( final Specification specification )
501     {
502         if ( specification == null )
503         {
504             throw new NullPointerException( "specification" );
505         }
506     }
507 
508     private void assertValidTemplates( final Implementation implementation )
509     {
510         if ( implementation == null )
511         {
512             throw new NullPointerException( "implementation" );
513         }
514 
515         final Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
516 
517         if ( messages != null )
518         {
519             for ( int i = messages.getMessage().size() - 1; i >= 0; i-- )
520             {
521                 final Message m = messages.getMessage().get( i );
522 
523                 if ( m.getTemplate() != null )
524                 {
525                     for ( int j = m.getTemplate().getText().size() - 1; j >= 0; j-- )
526                     {
527                         new MessageFormat( m.getTemplate().getText().get( j ).getValue() );
528                     }
529                 }
530             }
531         }
532     }
533 
534     private void writePropertiesFile( final Properties properties, final String comments, final File propertiesFile )
535         throws IOException
536     {
537         RandomAccessFile randomAccessFile = null;
538         FileChannel fileChannel = null;
539         FileLock fileLock = null;
540         boolean suppressExceptionOnClose = true;
541 
542         final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
543         properties.store( byteStream, comments );
544         byteStream.close();
545 
546         final byte[] bytes = byteStream.toByteArray();
547 
548         try
549         {
550             randomAccessFile = new RandomAccessFile( propertiesFile, "rw" );
551             fileChannel = randomAccessFile.getChannel();
552             fileLock = fileChannel.lock();
553             fileChannel.truncate( bytes.length );
554             fileChannel.position( 0L );
555             fileChannel.write( ByteBuffer.wrap( bytes ) );
556             fileChannel.force( true );
557             suppressExceptionOnClose = false;
558         }
559         finally
560         {
561             this.releaseAndClose( fileLock, fileChannel, randomAccessFile, suppressExceptionOnClose );
562         }
563     }
564 
565     private void releaseAndClose( final FileLock fileLock, final FileChannel fileChannel,
566                                   final Closeable closeable, final boolean suppressExceptions )
567         throws IOException
568     {
569         try
570         {
571             if ( fileLock != null )
572             {
573                 fileLock.release();
574             }
575         }
576         catch ( final IOException e )
577         {
578             if ( suppressExceptions )
579             {
580                 this.log( Level.SEVERE, null, e );
581             }
582             else
583             {
584                 throw e;
585             }
586         }
587         finally
588         {
589             try
590             {
591                 if ( fileChannel != null )
592                 {
593                     fileChannel.close();
594                 }
595             }
596             catch ( final IOException e )
597             {
598                 if ( suppressExceptions )
599                 {
600                     this.log( Level.SEVERE, null, e );
601                 }
602                 else
603                 {
604                     throw e;
605                 }
606             }
607             finally
608             {
609                 try
610                 {
611                     if ( closeable != null )
612                     {
613                         closeable.close();
614                     }
615                 }
616                 catch ( final IOException e )
617                 {
618                     if ( suppressExceptions )
619                     {
620                         this.log( Level.SEVERE, null, e );
621                     }
622                     else
623                     {
624                         throw e;
625                     }
626                 }
627             }
628         }
629     }
630 
631     private static String getMessage( final String key, final Object... arguments )
632     {
633         if ( key == null )
634         {
635             throw new NullPointerException( "key" );
636         }
637 
638         return MessageFormat.format( ResourceBundle.getBundle(
639             ResourceFileProcessor.class.getName().replace( '.', '/' ) ).getString( key ), arguments );
640 
641     }
642 
643 }