001    /*
002     *   Copyright (C) Christian Schulte, 2005-206
003     *   All rights reserved.
004     *
005     *   Redistribution and use in source and binary forms, with or without
006     *   modification, are permitted provided that the following conditions
007     *   are met:
008     *
009     *     o Redistributions of source code must retain the above copyright
010     *       notice, this list of conditions and the following disclaimer.
011     *
012     *     o Redistributions in binary form must reproduce the above copyright
013     *       notice, this list of conditions and the following disclaimer in
014     *       the documentation and/or other materials provided with the
015     *       distribution.
016     *
017     *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
018     *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
019     *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
020     *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
021     *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
022     *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
023     *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
024     *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025     *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
026     *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027     *
028     *   $JOMC: ResourceFileProcessor.java 3868 2011-10-14 13:23:09Z schulte2005 $
029     *
030     */
031    package org.jomc.tools;
032    
033    import java.io.File;
034    import java.io.FileOutputStream;
035    import java.io.IOException;
036    import java.io.OutputStream;
037    import java.text.MessageFormat;
038    import java.util.HashMap;
039    import java.util.Locale;
040    import java.util.Map;
041    import java.util.Properties;
042    import java.util.ResourceBundle;
043    import java.util.logging.Level;
044    import org.apache.velocity.VelocityContext;
045    import org.jomc.model.Implementation;
046    import org.jomc.model.Message;
047    import org.jomc.model.Messages;
048    import org.jomc.model.Module;
049    import org.jomc.model.Specification;
050    import org.jomc.model.Text;
051    
052    /**
053     * Processes resource files.
054     *
055     * <p><b>Use Cases:</b><br/><ul>
056     * <li>{@link #writeResourceBundleResourceFiles(File) }</li>
057     * <li>{@link #writeResourceBundleResourceFiles(Module, File) }</li>
058     * <li>{@link #writeResourceBundleResourceFiles(Specification, File) }</li>
059     * <li>{@link #writeResourceBundleResourceFiles(Implementation, File) }</li>
060     * </ul></p>
061     *
062     * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a>
063     * @version $JOMC: ResourceFileProcessor.java 3868 2011-10-14 13:23:09Z schulte2005 $
064     *
065     * @see #getModules()
066     */
067    public class ResourceFileProcessor extends JomcTool
068    {
069    
070        /** The language of the default language properties file of generated resource bundle resources. */
071        private Locale resourceBundleDefaultLocale;
072    
073        /** Creates a new {@code ResourceFileProcessor} instance. */
074        public ResourceFileProcessor()
075        {
076            super();
077        }
078    
079        /**
080         * Creates a new {@code ResourceFileProcessor} instance taking a {@code ResourceFileProcessor} instance to
081         * initialize the instance with.
082         *
083         * @param tool The instance to initialize the new instance with.
084         *
085         * @throws NullPointerException if {@code tool} is {@code null}.
086         * @throws IOException if copying {@code tool} fails.
087         */
088        public ResourceFileProcessor( final ResourceFileProcessor tool ) throws IOException
089        {
090            super( tool );
091            this.resourceBundleDefaultLocale = tool.resourceBundleDefaultLocale;
092        }
093    
094        /**
095         * Gets the language of the default language properties file of generated resource bundle resource files.
096         *
097         * @return The language of the default language properties file of generated resource bundle resource files.
098         *
099         * @see #setResourceBundleDefaultLocale(java.util.Locale)
100         */
101        public final Locale getResourceBundleDefaultLocale()
102        {
103            if ( this.resourceBundleDefaultLocale == null )
104            {
105                this.resourceBundleDefaultLocale = Locale.ENGLISH;
106    
107                if ( this.isLoggable( Level.CONFIG ) )
108                {
109                    this.log( Level.CONFIG,
110                              getMessage( "defaultResourceBundleDefaultLocale", this.resourceBundleDefaultLocale ), null );
111    
112                }
113            }
114    
115            return this.resourceBundleDefaultLocale;
116        }
117    
118        /**
119         * Sets the language of the default language properties file of generated resource bundle resource files.
120         *
121         * @param value The language of the default language properties file of generated resource bundle resource files.
122         *
123         * @see #getResourceBundleDefaultLocale()
124         */
125        public final void setResourceBundleDefaultLocale( final Locale value )
126        {
127            this.resourceBundleDefaultLocale = value;
128        }
129    
130        /**
131         * Writes resource bundle resource files of the modules of the instance to a given directory.
132         *
133         * @param resourcesDirectory The directory to write resource bundle resource files to.
134         *
135         * @throws NullPointerException if {@code resourcesDirectory} is {@code null}.
136         * @throws IOException if writing resource bundle resource files fails.
137         *
138         * @see #writeResourceBundleResourceFiles(org.jomc.model.Module, java.io.File)
139         */
140        public void writeResourceBundleResourceFiles( final File resourcesDirectory ) throws IOException
141        {
142            if ( resourcesDirectory == null )
143            {
144                throw new NullPointerException( "resourcesDirectory" );
145            }
146    
147            for ( int i = 0, s0 = this.getModules().getModule().size(); i < s0; i++ )
148            {
149                this.writeResourceBundleResourceFiles( this.getModules().getModule().get( i ), resourcesDirectory );
150            }
151        }
152    
153        /**
154         * Writes resource bundle resource files of a given module from the modules of the instance to a given directory.
155         *
156         * @param module The module to process.
157         * @param resourcesDirectory The directory to write resource bundle resource files to.
158         *
159         * @throws NullPointerException if {@code module} or {@code resourcesDirectory} is {@code null}.
160         * @throws IOException if writing resource bundle resource files fails.
161         *
162         * @see #writeResourceBundleResourceFiles(org.jomc.model.Specification, java.io.File)
163         * @see #writeResourceBundleResourceFiles(org.jomc.model.Implementation, java.io.File)
164         */
165        public void writeResourceBundleResourceFiles( final Module module, final File resourcesDirectory )
166            throws IOException
167        {
168            if ( module == null )
169            {
170                throw new NullPointerException( "module" );
171            }
172            if ( resourcesDirectory == null )
173            {
174                throw new NullPointerException( "resourcesDirectory" );
175            }
176    
177            assert this.getModules().getModule( module.getName() ) != null : "Module '" + module.getName() + "' not found.";
178    
179            if ( module.getSpecifications() != null )
180            {
181                for ( int i = 0, s0 = module.getSpecifications().getSpecification().size(); i < s0; i++ )
182                {
183                    this.writeResourceBundleResourceFiles( module.getSpecifications().getSpecification().get( i ),
184                                                           resourcesDirectory );
185    
186                }
187            }
188    
189            if ( module.getImplementations() != null )
190            {
191                for ( int i = 0, s0 = module.getImplementations().getImplementation().size(); i < s0; i++ )
192                {
193                    this.writeResourceBundleResourceFiles( module.getImplementations().getImplementation().get( i ),
194                                                           resourcesDirectory );
195    
196                }
197            }
198        }
199    
200        /**
201         * Writes resource bundle resource files of a given specification from the modules of the instance to a directory.
202         *
203         * @param specification The specification to process.
204         * @param resourcesDirectory The directory to write resource bundle resource files to.
205         *
206         * @throws NullPointerException if {@code specification} or {@code resourcesDirectory} is {@code null}.
207         * @throws IOException if writing resource bundle resource files fails.
208         *
209         * @see #getResourceBundleResources(org.jomc.model.Specification)
210         */
211        public void writeResourceBundleResourceFiles( final Specification specification, final File resourcesDirectory )
212            throws IOException
213        {
214            if ( specification == null )
215            {
216                throw new NullPointerException( "implementation" );
217            }
218            if ( resourcesDirectory == null )
219            {
220                throw new NullPointerException( "resourcesDirectory" );
221            }
222    
223            assert this.getModules().getSpecification( specification.getIdentifier() ) != null :
224                "Specification '" + specification.getIdentifier() + "' not found.";
225    
226            if ( specification.isClassDeclaration() )
227            {
228                if ( !resourcesDirectory.isDirectory() )
229                {
230                    throw new IOException( getMessage( "directoryNotFound", resourcesDirectory.getAbsolutePath() ) );
231                }
232    
233                this.assertValidTemplates( specification );
234    
235                final String bundlePath =
236                    this.getJavaTypeName( specification, true ).replace( '.', File.separatorChar );
237    
238                this.writeResourceBundleResourceFiles(
239                    this.getResourceBundleResources( specification ), resourcesDirectory, bundlePath );
240    
241            }
242        }
243    
244        /**
245         * Writes resource bundle resource files of a given implementation from the modules of the instance to a directory.
246         *
247         * @param implementation The implementation to process.
248         * @param resourcesDirectory The directory to write resource bundle resource files to.
249         *
250         * @throws NullPointerException if {@code implementation} or {@code resourcesDirectory} is {@code null}.
251         * @throws IOException if writing resource bundle resource files fails.
252         *
253         * @see #getResourceBundleResources(org.jomc.model.Implementation)
254         */
255        public void writeResourceBundleResourceFiles( final Implementation implementation, final File resourcesDirectory )
256            throws IOException
257        {
258            if ( implementation == null )
259            {
260                throw new NullPointerException( "implementation" );
261            }
262            if ( resourcesDirectory == null )
263            {
264                throw new NullPointerException( "resourcesDirectory" );
265            }
266    
267            assert this.getModules().getImplementation( implementation.getIdentifier() ) != null :
268                "Implementation '" + implementation.getIdentifier() + "' not found.";
269    
270            if ( implementation.isClassDeclaration() )
271            {
272                if ( !resourcesDirectory.isDirectory() )
273                {
274                    throw new IOException( getMessage( "directoryNotFound", resourcesDirectory.getAbsolutePath() ) );
275                }
276    
277                this.assertValidTemplates( implementation );
278    
279                final String bundlePath =
280                    this.getJavaTypeName( implementation, true ).replace( '.', File.separatorChar );
281    
282                this.writeResourceBundleResourceFiles(
283                    this.getResourceBundleResources( implementation ), resourcesDirectory, bundlePath );
284    
285            }
286        }
287    
288        /**
289         * Gets resource bundle properties resources of a given specification.
290         *
291         * @param specification The specification to get resource bundle properties resources of.
292         *
293         * @return Resource bundle properties resources of {@code specification}.
294         *
295         * @throws NullPointerException if {@code specification} is {@code null}.
296         * @throws IOException if getting the resource bundle properties resources fails.
297         */
298        public Map<Locale, Properties> getResourceBundleResources( final Specification specification )
299            throws IOException
300        {
301            if ( specification == null )
302            {
303                throw new NullPointerException( "specification" );
304            }
305    
306            assert this.getModules().getSpecification( specification.getIdentifier() ) != null :
307                "Specification '" + specification.getIdentifier() + "' not found.";
308    
309            return new HashMap<Locale, Properties>();
310        }
311    
312        /**
313         * Gets resource bundle properties resources of a given implementation.
314         *
315         * @param implementation The implementation to get resource bundle properties resources of.
316         *
317         * @return Resource bundle properties resources of {@code implementation}.
318         *
319         * @throws NullPointerException if {@code implementation} is {@code null}.
320         * @throws IOException if getting the resource bundle properties resources fails.
321         */
322        public Map<Locale, Properties> getResourceBundleResources( final Implementation implementation )
323            throws IOException
324        {
325            if ( implementation == null )
326            {
327                throw new NullPointerException( "implementation" );
328            }
329    
330            assert this.getModules().getImplementation( implementation.getIdentifier() ) != null :
331                "Implementation '" + implementation.getIdentifier() + "' not found.";
332    
333            final Map<Locale, java.util.Properties> properties = new HashMap<Locale, java.util.Properties>( 10 );
334            final Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
335    
336            if ( messages != null )
337            {
338                for ( int i = 0, s0 = messages.getMessage().size(); i < s0; i++ )
339                {
340                    final Message message = messages.getMessage().get( i );
341    
342                    if ( message.getTemplate() != null )
343                    {
344                        for ( int j = 0, s1 = message.getTemplate().getText().size(); j < s1; j++ )
345                        {
346                            final Text text = message.getTemplate().getText().get( j );
347                            final Locale locale = new Locale( text.getLanguage().toLowerCase() );
348                            Properties bundleProperties = properties.get( locale );
349    
350                            if ( bundleProperties == null )
351                            {
352                                bundleProperties = new Properties();
353                                properties.put( locale, bundleProperties );
354                            }
355    
356                            bundleProperties.setProperty( message.getName(), text.getValue() );
357                        }
358                    }
359                }
360            }
361    
362            return properties;
363        }
364    
365        private void writeResourceBundleResourceFiles( final Map<Locale, Properties> resources,
366                                                       final File resourcesDirectory, final String bundlePath )
367            throws IOException
368        {
369            if ( resources == null )
370            {
371                throw new NullPointerException( "resources" );
372            }
373            if ( resourcesDirectory == null )
374            {
375                throw new NullPointerException( "resourcesDirectory" );
376            }
377            if ( bundlePath == null )
378            {
379                throw new NullPointerException( "bundlePath" );
380            }
381    
382            Properties defProperties = null;
383            Properties fallbackProperties = null;
384    
385            final VelocityContext ctx = this.getVelocityContext();
386            final String toolName = ctx.get( "toolName" ).toString();
387            final String toolVersion = ctx.get( "toolVersion" ).toString();
388            final String toolUrl = ctx.get( "toolUrl" ).toString();
389    
390            for ( Map.Entry<Locale, Properties> e : resources.entrySet() )
391            {
392                final String language = e.getKey().getLanguage().toLowerCase();
393                final Properties p = e.getValue();
394                final File file = new File( resourcesDirectory, bundlePath + "_" + language + ".properties" );
395    
396                if ( this.getResourceBundleDefaultLocale().getLanguage().equalsIgnoreCase( language ) )
397                {
398                    defProperties = p;
399                }
400    
401                fallbackProperties = p;
402    
403                if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() )
404                {
405                    throw new IOException( getMessage( "failedCreatingDirectory",
406                                                       file.getParentFile().getAbsolutePath() ) );
407    
408                }
409    
410                if ( this.isLoggable( Level.INFO ) )
411                {
412                    this.log( Level.INFO, getMessage( "writing", file.getCanonicalPath() ), null );
413                }
414    
415                OutputStream out = null;
416                boolean suppressExceptionOnClose = true;
417                try
418                {
419                    out = new FileOutputStream( file );
420                    p.store( out, toolName + ' ' + toolVersion + " - See " + toolUrl );
421                    suppressExceptionOnClose = false;
422                }
423                finally
424                {
425                    try
426                    {
427                        if ( out != null )
428                        {
429                            out.close();
430                        }
431                    }
432                    catch ( final IOException ex )
433                    {
434                        if ( suppressExceptionOnClose )
435                        {
436                            this.log( Level.SEVERE, getMessage( ex ), ex );
437                        }
438                        else
439                        {
440                            throw ex;
441                        }
442                    }
443                }
444            }
445    
446            if ( defProperties == null )
447            {
448                defProperties = fallbackProperties;
449            }
450    
451            if ( defProperties != null )
452            {
453                final File file = new File( resourcesDirectory, bundlePath + ".properties" );
454                if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() )
455                {
456                    throw new IOException( getMessage( "failedCreatingDirectory",
457                                                       file.getParentFile().getAbsolutePath() ) );
458    
459                }
460    
461                if ( this.isLoggable( Level.INFO ) )
462                {
463                    this.log( Level.INFO, getMessage( "writing", file.getCanonicalPath() ), null );
464                }
465    
466                OutputStream out = null;
467                boolean suppressExceptionOnClose = true;
468                try
469                {
470                    out = new FileOutputStream( file );
471                    defProperties.store( out, toolName + ' ' + toolVersion + " - See " + toolUrl );
472                    suppressExceptionOnClose = false;
473                }
474                finally
475                {
476                    try
477                    {
478                        if ( out != null )
479                        {
480                            out.close();
481                        }
482                    }
483                    catch ( final IOException e )
484                    {
485                        if ( suppressExceptionOnClose )
486                        {
487                            this.log( Level.SEVERE, getMessage( e ), e );
488                        }
489                        else
490                        {
491                            throw e;
492                        }
493                    }
494                }
495            }
496        }
497    
498        private void assertValidTemplates( final Specification specification )
499        {
500            if ( specification == null )
501            {
502                throw new NullPointerException( "specification" );
503            }
504        }
505    
506        private void assertValidTemplates( final Implementation implementation )
507        {
508            if ( implementation == null )
509            {
510                throw new NullPointerException( "implementation" );
511            }
512    
513            final Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
514    
515            if ( messages != null )
516            {
517                for ( int i = messages.getMessage().size() - 1; i >= 0; i-- )
518                {
519                    final Message m = messages.getMessage().get( i );
520    
521                    if ( m.getTemplate() != null )
522                    {
523                        for ( int j = m.getTemplate().getText().size() - 1; j >= 0; j-- )
524                        {
525                            new MessageFormat( m.getTemplate().getText().get( j ).getValue() );
526                        }
527                    }
528                }
529            }
530        }
531    
532        private static String getMessage( final String key, final Object... arguments )
533        {
534            if ( key == null )
535            {
536                throw new NullPointerException( "key" );
537            }
538    
539            return MessageFormat.format( ResourceBundle.getBundle(
540                ResourceFileProcessor.class.getName().replace( '.', '/' ) ).getString( key ), arguments );
541    
542        }
543    
544        private static String getMessage( final Throwable t )
545        {
546            return t != null ? t.getMessage() != null ? t.getMessage() : getMessage( t.getCause() ) : null;
547        }
548    
549    }