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