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