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 744 2009-10-06 04:43:21Z 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 744 2009-10-06 04:43:21Z 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                this.log( Level.FINE, this.getMessage( "defaultLocale", new Object[]
121                    {
122                        this.defaultLocale.toString()
123                    } ), null );
124    
125            }
126    
127            return this.defaultLocale;
128        }
129    
130        /**
131         * Sets the language of the default language properties file of the bundle.
132         *
133         * @param value The language of the default language properties file of the bundle.
134         *
135         * @see #getDefaultLocale()
136         */
137        public void setDefaultLocale( final Locale value )
138        {
139            this.defaultLocale = value;
140        }
141    
142        /**
143         * Writes bundle sources of the modules of the instance to a given directory.
144         *
145         * @param sourcesDirectory The directory to write sources to.
146         *
147         * @throws NullPointerException if {@code sourcesDirectory} is {@code null}.
148         * @throws IOException if writing fails.
149         *
150         * @see #writeBundleSources(org.jomc.model.Module, java.io.File)
151         */
152        public void writeBundleSources( final File sourcesDirectory ) throws IOException
153        {
154            if ( sourcesDirectory == null )
155            {
156                throw new NullPointerException( "sourcesDirectory" );
157            }
158    
159            for ( Module m : this.getModules().getModule() )
160            {
161                this.writeBundleSources( m, sourcesDirectory );
162            }
163        }
164    
165        /**
166         * Writes bundle sources of a given module from the modules of the instance to a given directory.
167         *
168         * @param module The module to process.
169         * @param sourcesDirectory The directory to write sources to.
170         *
171         * @throws NullPointerException if {@code module} or {@code sourcesDirectory} is {@code null}.
172         * @throws IOException if writing fails.
173         *
174         * @see #writeBundleSources(org.jomc.model.Implementation, java.io.File)
175         */
176        public void writeBundleSources( final Module module, final File sourcesDirectory ) throws IOException
177        {
178            if ( module == null )
179            {
180                throw new NullPointerException( "module" );
181            }
182            if ( sourcesDirectory == null )
183            {
184                throw new NullPointerException( "sourcesDirectory" );
185            }
186    
187            if ( module.getImplementations() != null )
188            {
189                for ( Implementation i : module.getImplementations().getImplementation() )
190                {
191                    this.writeBundleSources( i, sourcesDirectory );
192                }
193            }
194        }
195    
196        /**
197         * Writes bundle sources of a given implementation from the modules of the instance to a given directory.
198         *
199         * @param implementation The implementation to process.
200         * @param sourcesDirectory The directory to write sources to.
201         *
202         * @throws NullPointerException if {@code implementation} or {@code sourcesDirectory} is {@code null}.
203         * @throws IOException if writing fails.
204         *
205         * @see #getResourceBundleSources(org.jomc.model.Implementation)
206         */
207        public void writeBundleSources( final Implementation implementation, final File sourcesDirectory )
208            throws IOException
209        {
210            if ( implementation == null )
211            {
212                throw new NullPointerException( "implementation" );
213            }
214            if ( sourcesDirectory == null )
215            {
216                throw new NullPointerException( "sourcesDirectory" );
217            }
218    
219            if ( this.isJavaClassDeclaration( implementation ) )
220            {
221                this.assertValidTemplates( implementation );
222    
223                final String bundlePath =
224                    ( this.getJavaTypeName( implementation, true ) + BUNDLE_SUFFIX ).replace( '.', File.separatorChar );
225    
226                final File bundleFile = new File( sourcesDirectory, bundlePath + ".java" );
227    
228                if ( !bundleFile.getParentFile().exists() && !bundleFile.getParentFile().mkdirs() )
229                {
230                    throw new IOException( this.getMessage( "failedCreatingDirectory", new Object[]
231                        {
232                            bundleFile.getParentFile().getAbsolutePath()
233                        } ) );
234    
235                }
236    
237                this.log( Level.INFO, this.getMessage( "writing", new Object[]
238                    {
239                        bundleFile.getCanonicalPath()
240                    } ), null );
241    
242                FileUtils.writeStringToFile( bundleFile, this.getResourceBundleSources( implementation ),
243                                             this.getOutputEncoding() );
244    
245            }
246        }
247    
248        /**
249         * Writes bundle resources of the modules of the instance to a given directory.
250         *
251         * @param resourcesDirectory The directory to write resources to.
252         *
253         * @throws NullPointerException if {@code resourcesDirectory} is {@code null}.
254         * @throws IOException if writing fails.
255         *
256         * @see #writeBundleResources(org.jomc.model.Module, java.io.File)
257         */
258        public void writeBundleResources( final File resourcesDirectory ) throws IOException
259        {
260            if ( resourcesDirectory == null )
261            {
262                throw new NullPointerException( "resourcesDirectory" );
263            }
264    
265            for ( Module m : this.getModules().getModule() )
266            {
267                this.writeBundleResources( m, resourcesDirectory );
268            }
269        }
270    
271        /**
272         * Writes bundle resources of a given module from the modules of the instance to a given directory.
273         *
274         * @param module The module to process.
275         * @param resourcesDirectory The directory to write resources to.
276         *
277         * @throws NullPointerException if {@code module} or {@code resourcesDirectory} is {@code null}.
278         * @throws IOException if writing fails.
279         *
280         * @see #writeBundleResources(org.jomc.model.Implementation, java.io.File)
281         */
282        public void writeBundleResources( final Module module, final File resourcesDirectory ) throws IOException
283        {
284            if ( module == null )
285            {
286                throw new NullPointerException( "module" );
287            }
288            if ( resourcesDirectory == null )
289            {
290                throw new NullPointerException( "resourcesDirectory" );
291            }
292    
293            if ( module.getImplementations() != null )
294            {
295                for ( Implementation i : module.getImplementations().getImplementation() )
296                {
297                    this.writeBundleResources( i, resourcesDirectory );
298                }
299            }
300        }
301    
302        /**
303         * Writes the bundle resources of a given implementation from the modules of the instance to a directory.
304         *
305         * @param implementation The implementation to process.
306         * @param resourcesDirectory The directory to write resources to.
307         *
308         * @throws NullPointerException if {@code implementation} or {@code resourcesDirectory} is {@code null}.
309         * @throws IOException if writing fails.
310         *
311         * @see #getResourceBundleResources(org.jomc.model.Implementation)
312         */
313        public void writeBundleResources( final Implementation implementation, final File resourcesDirectory )
314            throws IOException
315        {
316            if ( implementation == null )
317            {
318                throw new NullPointerException( "implementation" );
319            }
320            if ( resourcesDirectory == null )
321            {
322                throw new NullPointerException( "resourcesDirectory" );
323            }
324    
325            if ( this.isJavaClassDeclaration( implementation ) )
326            {
327                this.assertValidTemplates( implementation );
328    
329                final String bundlePath =
330                    ( this.getJavaTypeName( implementation, true ) + BUNDLE_SUFFIX ).replace( '.', File.separatorChar );
331    
332                Properties defProperties = null;
333                Properties fallbackProperties = null;
334    
335                for ( Map.Entry<Locale, Properties> e : this.getResourceBundleResources( implementation ).entrySet() )
336                {
337                    final String language = e.getKey().getLanguage().toLowerCase();
338                    final java.util.Properties p = e.getValue();
339                    final File file = new File( resourcesDirectory, bundlePath + "_" + language + ".properties" );
340    
341                    if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() )
342                    {
343                        throw new IOException( this.getMessage( "failedCreatingDirectory", new Object[]
344                            {
345                                file.getParentFile().getAbsolutePath()
346                            } ) );
347    
348                    }
349    
350                    this.log( Level.INFO, this.getMessage( "writing", new Object[]
351                        {
352                            file.getCanonicalPath()
353                        } ), null );
354    
355                    OutputStream out = null;
356                    try
357                    {
358                        out = new FileOutputStream( file );
359                        p.store( out, GENERATOR_NAME + ' ' + GENERATOR_VERSION );
360                    }
361                    finally
362                    {
363                        if ( out != null )
364                        {
365                            out.close();
366                        }
367                    }
368    
369                    if ( this.getDefaultLocale().getLanguage().equalsIgnoreCase( language ) )
370                    {
371                        defProperties = p;
372                    }
373    
374                    fallbackProperties = p;
375                }
376    
377                if ( defProperties == null )
378                {
379                    defProperties = fallbackProperties;
380                }
381    
382                if ( defProperties != null )
383                {
384                    final File file = new File( resourcesDirectory, bundlePath + ".properties" );
385                    if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() )
386                    {
387                        throw new IOException( this.getMessage( "failedCreatingDirectory", new Object[]
388                            {
389                                file.getParentFile().getAbsolutePath()
390                            } ) );
391    
392                    }
393    
394                    this.log( Level.INFO, this.getMessage( "writing", new Object[]
395                        {
396                            file.getCanonicalPath()
397                        } ), null );
398    
399                    OutputStream out = null;
400                    try
401                    {
402                        out = new FileOutputStream( file );
403                        defProperties.store( out, GENERATOR_NAME + ' ' + GENERATOR_VERSION );
404                    }
405                    finally
406                    {
407                        if ( out != null )
408                        {
409                            out.close();
410                        }
411                    }
412                }
413            }
414        }
415    
416        /**
417         * Gets the source code of the Java class for accessing the resource bundle of a given implementation.
418         *
419         * @param implementation The implementation to get the source code of.
420         *
421         * @return The source code of the Java class for accessing the resource bundle of {@code implementation}.
422         *
423         * @throws NullPointerException if {@code implementation} is {@code null}.
424         * @throws IOException if getting the source code fails.
425         */
426        public String getResourceBundleSources( final Implementation implementation ) throws IOException
427        {
428            if ( implementation == null )
429            {
430                throw new NullPointerException( "implementation" );
431            }
432    
433            final StringWriter writer = new StringWriter();
434            final VelocityContext ctx = this.getVelocityContext();
435            final Template template = this.getVelocityTemplate( BUNDLE_TEMPLATE );
436            ctx.put( "implementation", implementation );
437            ctx.put( "template", template );
438            template.merge( ctx, writer );
439            writer.close();
440            return writer.toString();
441        }
442    
443        /**
444         * Gets the resource bundle properties of a given implementation.
445         *
446         * @param implementation The implementation to get resource bundle properties of.
447         *
448         * @return Resource bundle properties of {@code implementation}.
449         *
450         * @throws NullPointerException if {@code implementation} is {@code null}.
451         * @throws IOException if getting the resources fails.
452         */
453        public Map<Locale, Properties> getResourceBundleResources( final Implementation implementation ) throws IOException
454        {
455            if ( implementation == null )
456            {
457                throw new NullPointerException( "implementation" );
458            }
459    
460            final Map<Locale, java.util.Properties> properties = new HashMap<Locale, java.util.Properties>( 10 );
461            final Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
462    
463            if ( messages != null )
464            {
465                for ( Message message : messages.getMessage() )
466                {
467                    if ( message.getTemplate() != null )
468                    {
469                        for ( Text text : message.getTemplate().getText() )
470                        {
471                            final Locale locale = new Locale( text.getLanguage().toLowerCase() );
472                            Properties bundleProperties = properties.get( locale );
473    
474                            if ( bundleProperties == null )
475                            {
476                                bundleProperties = new Properties();
477                                properties.put( locale, bundleProperties );
478                            }
479    
480                            bundleProperties.setProperty( message.getName(), text.getValue() );
481                        }
482                    }
483                }
484            }
485    
486            return properties;
487        }
488    
489        /**
490         * Gets the velocity context used for merging templates.
491         *
492         * @return The velocity context used for merging templates.
493         */
494        @Override
495        public VelocityContext getVelocityContext()
496        {
497            final VelocityContext ctx = super.getVelocityContext();
498            ctx.put( "classSuffix", BUNDLE_SUFFIX );
499            ctx.put( "comment", Boolean.TRUE );
500            return ctx;
501        }
502    
503        private void assertValidTemplates( final Implementation implementation )
504        {
505            if ( implementation == null )
506            {
507                throw new NullPointerException( "implementation" );
508            }
509    
510            final Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
511            if ( messages != null )
512            {
513                for ( Message m : messages.getMessage() )
514                {
515                    if ( m.getTemplate() != null )
516                    {
517                        for ( Text t : m.getTemplate().getText() )
518                        {
519                            new MessageFormat( t.getValue() );
520                        }
521                    }
522                }
523            }
524        }
525    
526        private String getMessage( final String key, final Object args )
527        {
528            final ResourceBundle b = ResourceBundle.getBundle( JavaBundles.class.getName().replace( '.', '/' ) );
529            final MessageFormat f = new MessageFormat( b.getString( key ) );
530            return f.format( args );
531        }
532    
533    }