001    /*
002     *   Copyright (c) 2009 The JOMC Project
003     *   Copyright (c) 2005 Christian Schulte <cs@jomc.org>
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: JavaBundles.java 891 2009-11-02 03:40:00Z 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.io.StringWriter;
040    import java.text.MessageFormat;
041    import java.util.HashMap;
042    import java.util.Locale;
043    import java.util.Map;
044    import java.util.Properties;
045    import java.util.ResourceBundle;
046    import java.util.logging.Level;
047    import org.apache.commons.io.FileUtils;
048    import org.apache.velocity.Template;
049    import org.apache.velocity.VelocityContext;
050    import org.jomc.model.Implementation;
051    import org.jomc.model.Message;
052    import org.jomc.model.Messages;
053    import org.jomc.model.Module;
054    import org.jomc.model.Text;
055    
056    /**
057     * Generates Java bundles.
058     *
059     * <p><b>Use cases</b><br/><ul>
060     * <li>{@link #writeBundleResources(java.io.File) }</li>
061     * <li>{@link #writeBundleResources(org.jomc.model.Module, java.io.File) }</li>
062     * <li>{@link #writeBundleResources(org.jomc.model.Implementation, java.io.File) }</li>
063     * <li>{@link #writeBundleSources(java.io.File) }</li>
064     * <li>{@link #writeBundleSources(org.jomc.model.Module, java.io.File) }</li>
065     * <li>{@link #writeBundleSources(org.jomc.model.Implementation, java.io.File) }</li>
066     * </ul></p>
067     *
068     * @author <a href="mailto:cs@jomc.org">Christian Schulte</a>
069     * @version $Id: JavaBundles.java 891 2009-11-02 03:40:00Z schulte2005 $
070     *
071     * @see #getModules()
072     */
073    public class JavaBundles extends JomcTool
074    {
075    
076        /** Name of the generator. */
077        private static final String GENERATOR_NAME = JavaBundles.class.getName();
078    
079        /** Constant for the version of the generator. */
080        private static final String GENERATOR_VERSION = "1.0";
081    
082        /** Location of the {@code Bundle.java.vm} template. */
083        private static final String BUNDLE_TEMPLATE = "Bundle.java.vm";
084    
085        /** Constant for the suffix appended to implementation identifiers. */
086        private static final String BUNDLE_SUFFIX = "Bundle";
087    
088        /** The language of the default language properties file of the bundle. */
089        private Locale defaultLocale;
090    
091        /** Creates a new {@code JavaBundles} instance. */
092        public JavaBundles()
093        {
094            super();
095        }
096    
097        /**
098         * Creates a new {@code JavaBundles} instance taking a {@code JavaBundles} instance to initialize the instance with.
099         *
100         * @param tool The instance to initialize the new instance with,
101         */
102        public JavaBundles( final JavaBundles tool )
103        {
104            super( tool );
105            this.setDefaultLocale( tool.getDefaultLocale() );
106        }
107    
108        /**
109         * Gets the language of the default language properties file of the bundle.
110         *
111         * @return The language of the default language properties file of the bundle.
112         *
113         * @see #setDefaultLocale(java.util.Locale)
114         */
115        public Locale getDefaultLocale()
116        {
117            if ( this.defaultLocale == null )
118            {
119                this.defaultLocale = Locale.getDefault();
120                if ( this.isLoggable( Level.FINE ) )
121                {
122                    this.log( Level.FINE, this.getMessage( "defaultLocale", new Object[]
123                        {
124                            this.defaultLocale.toString()
125                        } ), null );
126    
127                }
128            }
129    
130            return this.defaultLocale;
131        }
132    
133        /**
134         * Sets the language of the default language properties file of the bundle.
135         *
136         * @param value The language of the default language properties file of the bundle.
137         *
138         * @see #getDefaultLocale()
139         */
140        public void setDefaultLocale( final Locale value )
141        {
142            this.defaultLocale = value;
143        }
144    
145        /**
146         * Writes bundle sources of the modules of the instance to a given directory.
147         *
148         * @param sourcesDirectory The directory to write sources to.
149         *
150         * @throws NullPointerException if {@code sourcesDirectory} is {@code null}.
151         * @throws IOException if writing fails.
152         *
153         * @see #writeBundleSources(org.jomc.model.Module, java.io.File)
154         */
155        public void writeBundleSources( final File sourcesDirectory ) throws IOException
156        {
157            if ( sourcesDirectory == null )
158            {
159                throw new NullPointerException( "sourcesDirectory" );
160            }
161    
162            for ( Module m : this.getModules().getModule() )
163            {
164                this.writeBundleSources( m, sourcesDirectory );
165            }
166        }
167    
168        /**
169         * Writes bundle sources of a given module from the modules of the instance to a given directory.
170         *
171         * @param module The module to process.
172         * @param sourcesDirectory The directory to write sources to.
173         *
174         * @throws NullPointerException if {@code module} or {@code sourcesDirectory} is {@code null}.
175         * @throws IOException if writing fails.
176         *
177         * @see #writeBundleSources(org.jomc.model.Implementation, java.io.File)
178         */
179        public void writeBundleSources( final Module module, final File sourcesDirectory ) throws IOException
180        {
181            if ( module == null )
182            {
183                throw new NullPointerException( "module" );
184            }
185            if ( sourcesDirectory == null )
186            {
187                throw new NullPointerException( "sourcesDirectory" );
188            }
189    
190            if ( module.getImplementations() != null )
191            {
192                for ( Implementation i : module.getImplementations().getImplementation() )
193                {
194                    this.writeBundleSources( i, sourcesDirectory );
195                }
196            }
197        }
198    
199        /**
200         * Writes bundle sources of a given implementation from the modules of the instance to a given directory.
201         *
202         * @param implementation The implementation to process.
203         * @param sourcesDirectory The directory to write sources to.
204         *
205         * @throws NullPointerException if {@code implementation} or {@code sourcesDirectory} is {@code null}.
206         * @throws IOException if writing fails.
207         *
208         * @see #getResourceBundleSources(org.jomc.model.Implementation)
209         */
210        public void writeBundleSources( final Implementation implementation, final File sourcesDirectory )
211            throws IOException
212        {
213            if ( implementation == null )
214            {
215                throw new NullPointerException( "implementation" );
216            }
217            if ( sourcesDirectory == null )
218            {
219                throw new NullPointerException( "sourcesDirectory" );
220            }
221    
222            if ( this.isJavaClassDeclaration( implementation ) )
223            {
224                this.assertValidTemplates( implementation );
225    
226                final String bundlePath =
227                    ( this.getJavaTypeName( implementation, true ) + BUNDLE_SUFFIX ).replace( '.', File.separatorChar );
228    
229                final File bundleFile = new File( sourcesDirectory, bundlePath + ".java" );
230    
231                if ( !bundleFile.getParentFile().exists() && !bundleFile.getParentFile().mkdirs() )
232                {
233                    throw new IOException( this.getMessage( "failedCreatingDirectory", new Object[]
234                        {
235                            bundleFile.getParentFile().getAbsolutePath()
236                        } ) );
237    
238                }
239    
240                if ( this.isLoggable( Level.INFO ) )
241                {
242                    this.log( Level.INFO, this.getMessage( "writing", new Object[]
243                        {
244                            bundleFile.getCanonicalPath()
245                        } ), null );
246    
247                }
248    
249                FileUtils.writeStringToFile( bundleFile, this.getResourceBundleSources( implementation ),
250                                             this.getOutputEncoding() );
251    
252            }
253        }
254    
255        /**
256         * Writes bundle resources of the modules of the instance to a given directory.
257         *
258         * @param resourcesDirectory The directory to write resources to.
259         *
260         * @throws NullPointerException if {@code resourcesDirectory} is {@code null}.
261         * @throws IOException if writing fails.
262         *
263         * @see #writeBundleResources(org.jomc.model.Module, java.io.File)
264         */
265        public void writeBundleResources( final File resourcesDirectory ) throws IOException
266        {
267            if ( resourcesDirectory == null )
268            {
269                throw new NullPointerException( "resourcesDirectory" );
270            }
271    
272            for ( Module m : this.getModules().getModule() )
273            {
274                this.writeBundleResources( m, resourcesDirectory );
275            }
276        }
277    
278        /**
279         * Writes bundle resources of a given module from the modules of the instance to a given directory.
280         *
281         * @param module The module to process.
282         * @param resourcesDirectory The directory to write resources to.
283         *
284         * @throws NullPointerException if {@code module} or {@code resourcesDirectory} is {@code null}.
285         * @throws IOException if writing fails.
286         *
287         * @see #writeBundleResources(org.jomc.model.Implementation, java.io.File)
288         */
289        public void writeBundleResources( final Module module, final File resourcesDirectory ) throws IOException
290        {
291            if ( module == null )
292            {
293                throw new NullPointerException( "module" );
294            }
295            if ( resourcesDirectory == null )
296            {
297                throw new NullPointerException( "resourcesDirectory" );
298            }
299    
300            if ( module.getImplementations() != null )
301            {
302                for ( Implementation i : module.getImplementations().getImplementation() )
303                {
304                    this.writeBundleResources( i, resourcesDirectory );
305                }
306            }
307        }
308    
309        /**
310         * Writes the bundle resources of a given implementation from the modules of the instance to a directory.
311         *
312         * @param implementation The implementation to process.
313         * @param resourcesDirectory The directory to write resources to.
314         *
315         * @throws NullPointerException if {@code implementation} or {@code resourcesDirectory} is {@code null}.
316         * @throws IOException if writing fails.
317         *
318         * @see #getResourceBundleResources(org.jomc.model.Implementation)
319         */
320        public void writeBundleResources( final Implementation implementation, final File resourcesDirectory )
321            throws IOException
322        {
323            if ( implementation == null )
324            {
325                throw new NullPointerException( "implementation" );
326            }
327            if ( resourcesDirectory == null )
328            {
329                throw new NullPointerException( "resourcesDirectory" );
330            }
331    
332            if ( this.isJavaClassDeclaration( implementation ) )
333            {
334                this.assertValidTemplates( implementation );
335    
336                final String bundlePath =
337                    ( this.getJavaTypeName( implementation, true ) + BUNDLE_SUFFIX ).replace( '.', File.separatorChar );
338    
339                Properties defProperties = null;
340                Properties fallbackProperties = null;
341    
342                for ( Map.Entry<Locale, Properties> e : this.getResourceBundleResources( implementation ).entrySet() )
343                {
344                    final String language = e.getKey().getLanguage().toLowerCase();
345                    final java.util.Properties p = e.getValue();
346                    final File file = new File( resourcesDirectory, bundlePath + "_" + language + ".properties" );
347    
348                    if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() )
349                    {
350                        throw new IOException( this.getMessage( "failedCreatingDirectory", new Object[]
351                            {
352                                file.getParentFile().getAbsolutePath()
353                            } ) );
354    
355                    }
356    
357                    if ( this.isLoggable( Level.INFO ) )
358                    {
359                        this.log( Level.INFO, this.getMessage( "writing", new Object[]
360                            {
361                                file.getCanonicalPath()
362                            } ), null );
363    
364                    }
365    
366                    OutputStream out = null;
367                    try
368                    {
369                        out = new FileOutputStream( file );
370                        p.store( out, GENERATOR_NAME + ' ' + GENERATOR_VERSION );
371                    }
372                    finally
373                    {
374                        if ( out != null )
375                        {
376                            out.close();
377                        }
378                    }
379    
380                    if ( this.getDefaultLocale().getLanguage().equalsIgnoreCase( language ) )
381                    {
382                        defProperties = p;
383                    }
384    
385                    fallbackProperties = p;
386                }
387    
388                if ( defProperties == null )
389                {
390                    defProperties = fallbackProperties;
391                }
392    
393                if ( defProperties != null )
394                {
395                    final File file = new File( resourcesDirectory, bundlePath + ".properties" );
396                    if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() )
397                    {
398                        throw new IOException( this.getMessage( "failedCreatingDirectory", new Object[]
399                            {
400                                file.getParentFile().getAbsolutePath()
401                            } ) );
402    
403                    }
404    
405                    if ( this.isLoggable( Level.INFO ) )
406                    {
407                        this.log( Level.INFO, this.getMessage( "writing", new Object[]
408                            {
409                                file.getCanonicalPath()
410                            } ), null );
411    
412                    }
413    
414                    OutputStream out = null;
415                    try
416                    {
417                        out = new FileOutputStream( file );
418                        defProperties.store( out, GENERATOR_NAME + ' ' + GENERATOR_VERSION );
419                    }
420                    finally
421                    {
422                        if ( out != null )
423                        {
424                            out.close();
425                        }
426                    }
427                }
428            }
429        }
430    
431        /**
432         * Gets the source code of the Java class for accessing the resource bundle of a given implementation.
433         *
434         * @param implementation The implementation to get the source code of.
435         *
436         * @return The source code of the Java class for accessing the resource bundle of {@code implementation}.
437         *
438         * @throws NullPointerException if {@code implementation} is {@code null}.
439         * @throws IOException if getting the source code fails.
440         */
441        public String getResourceBundleSources( final Implementation implementation ) throws IOException
442        {
443            if ( implementation == null )
444            {
445                throw new NullPointerException( "implementation" );
446            }
447    
448            final StringWriter writer = new StringWriter();
449            final VelocityContext ctx = this.getVelocityContext();
450            final Template template = this.getVelocityTemplate( BUNDLE_TEMPLATE );
451            ctx.put( "implementation", implementation );
452            ctx.put( "template", template );
453            template.merge( ctx, writer );
454            writer.close();
455            return writer.toString();
456        }
457    
458        /**
459         * Gets the resource bundle properties of a given implementation.
460         *
461         * @param implementation The implementation to get resource bundle properties of.
462         *
463         * @return Resource bundle properties of {@code implementation}.
464         *
465         * @throws NullPointerException if {@code implementation} is {@code null}.
466         * @throws IOException if getting the resources fails.
467         */
468        public Map<Locale, Properties> getResourceBundleResources( final Implementation implementation ) throws IOException
469        {
470            if ( implementation == null )
471            {
472                throw new NullPointerException( "implementation" );
473            }
474    
475            final Map<Locale, java.util.Properties> properties = new HashMap<Locale, java.util.Properties>( 10 );
476            final Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
477    
478            if ( messages != null )
479            {
480                for ( Message message : messages.getMessage() )
481                {
482                    if ( message.getTemplate() != null )
483                    {
484                        for ( Text text : message.getTemplate().getText() )
485                        {
486                            final Locale locale = new Locale( text.getLanguage().toLowerCase() );
487                            Properties bundleProperties = properties.get( locale );
488    
489                            if ( bundleProperties == null )
490                            {
491                                bundleProperties = new Properties();
492                                properties.put( locale, bundleProperties );
493                            }
494    
495                            bundleProperties.setProperty( message.getName(), text.getValue() );
496                        }
497                    }
498                }
499            }
500    
501            return properties;
502        }
503    
504        /**
505         * Gets the velocity context used for merging templates.
506         *
507         * @return The velocity context used for merging templates.
508         */
509        @Override
510        public VelocityContext getVelocityContext()
511        {
512            final VelocityContext ctx = super.getVelocityContext();
513            ctx.put( "classSuffix", BUNDLE_SUFFIX );
514            ctx.put( "comment", Boolean.TRUE );
515            return ctx;
516        }
517    
518        private void assertValidTemplates( final Implementation implementation )
519        {
520            if ( implementation == null )
521            {
522                throw new NullPointerException( "implementation" );
523            }
524    
525            final Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
526            if ( messages != null )
527            {
528                for ( Message m : messages.getMessage() )
529                {
530                    if ( m.getTemplate() != null )
531                    {
532                        for ( Text t : m.getTemplate().getText() )
533                        {
534                            new MessageFormat( t.getValue() );
535                        }
536                    }
537                }
538            }
539        }
540    
541        private String getMessage( final String key, final Object args )
542        {
543            final ResourceBundle b = ResourceBundle.getBundle( JavaBundles.class.getName().replace( '.', '/' ) );
544            final MessageFormat f = new MessageFormat( b.getString( key ) );
545            return f.format( args );
546        }
547    
548    }