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