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 1237 2010-01-09 20:22:54Z 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 1237 2010-01-09 20:22:54Z 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 ToolException if writing fails.
152         *
153         * @see #writeBundleSources(org.jomc.model.Module, java.io.File)
154         */
155        public void writeBundleSources( final File sourcesDirectory ) throws ToolException
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 ToolException 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 ToolException
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 ToolException if writing fails.
207         *
208         * @see #getResourceBundleSources(org.jomc.model.Implementation)
209         */
210        public void writeBundleSources( final Implementation implementation, final File sourcesDirectory )
211            throws ToolException
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            try
223            {
224                if ( implementation.isClassDeclaration() )
225                {
226                    this.assertValidTemplates( implementation );
227    
228                    final String bundlePath =
229                        ( this.getJavaTypeName( implementation, true ) + BUNDLE_SUFFIX ).replace( '.', File.separatorChar );
230    
231                    final File bundleFile = new File( sourcesDirectory, bundlePath + ".java" );
232    
233                    if ( !bundleFile.getParentFile().exists() && !bundleFile.getParentFile().mkdirs() )
234                    {
235                        throw new ToolException( this.getMessage( "failedCreatingDirectory", new Object[]
236                            {
237                                bundleFile.getParentFile().getAbsolutePath()
238                            } ) );
239    
240                    }
241    
242                    if ( this.isLoggable( Level.INFO ) )
243                    {
244                        this.log( Level.INFO, this.getMessage( "writing", new Object[]
245                            {
246                                bundleFile.getCanonicalPath()
247                            } ), null );
248    
249                    }
250    
251                    FileUtils.writeStringToFile( bundleFile, this.getResourceBundleSources( implementation ),
252                                                 this.getOutputEncoding() );
253    
254                }
255            }
256            catch ( final IOException e )
257            {
258                throw new ToolException( e );
259            }
260        }
261    
262        /**
263         * Writes bundle resources of the modules of the instance to a given directory.
264         *
265         * @param resourcesDirectory The directory to write resources to.
266         *
267         * @throws NullPointerException if {@code resourcesDirectory} is {@code null}.
268         * @throws ToolException if writing fails.
269         *
270         * @see #writeBundleResources(org.jomc.model.Module, java.io.File)
271         */
272        public void writeBundleResources( final File resourcesDirectory ) throws ToolException
273        {
274            if ( resourcesDirectory == null )
275            {
276                throw new NullPointerException( "resourcesDirectory" );
277            }
278    
279            for ( Module m : this.getModules().getModule() )
280            {
281                this.writeBundleResources( m, resourcesDirectory );
282            }
283        }
284    
285        /**
286         * Writes bundle resources of a given module from the modules of the instance to a given directory.
287         *
288         * @param module The module to process.
289         * @param resourcesDirectory The directory to write resources to.
290         *
291         * @throws NullPointerException if {@code module} or {@code resourcesDirectory} is {@code null}.
292         * @throws ToolException if writing fails.
293         *
294         * @see #writeBundleResources(org.jomc.model.Implementation, java.io.File)
295         */
296        public void writeBundleResources( final Module module, final File resourcesDirectory ) throws ToolException
297        {
298            if ( module == null )
299            {
300                throw new NullPointerException( "module" );
301            }
302            if ( resourcesDirectory == null )
303            {
304                throw new NullPointerException( "resourcesDirectory" );
305            }
306    
307            if ( module.getImplementations() != null )
308            {
309                for ( Implementation i : module.getImplementations().getImplementation() )
310                {
311                    this.writeBundleResources( i, resourcesDirectory );
312                }
313            }
314        }
315    
316        /**
317         * Writes the bundle resources of a given implementation from the modules of the instance to a directory.
318         *
319         * @param implementation The implementation to process.
320         * @param resourcesDirectory The directory to write resources to.
321         *
322         * @throws NullPointerException if {@code implementation} or {@code resourcesDirectory} is {@code null}.
323         * @throws ToolException if writing fails.
324         *
325         * @see #getResourceBundleResources(org.jomc.model.Implementation)
326         */
327        public void writeBundleResources( final Implementation implementation, final File resourcesDirectory )
328            throws ToolException
329        {
330            if ( implementation == null )
331            {
332                throw new NullPointerException( "implementation" );
333            }
334            if ( resourcesDirectory == null )
335            {
336                throw new NullPointerException( "resourcesDirectory" );
337            }
338    
339            try
340            {
341                if ( implementation.isClassDeclaration() )
342                {
343                    this.assertValidTemplates( implementation );
344    
345                    final String bundlePath =
346                        ( this.getJavaTypeName( implementation, true ) + BUNDLE_SUFFIX ).replace( '.', File.separatorChar );
347    
348                    Properties defProperties = null;
349                    Properties fallbackProperties = null;
350    
351                    for ( Map.Entry<Locale, Properties> e : this.getResourceBundleResources( implementation ).entrySet() )
352                    {
353                        final String language = e.getKey().getLanguage().toLowerCase();
354                        final java.util.Properties p = e.getValue();
355                        final File file = new File( resourcesDirectory, bundlePath + "_" + language + ".properties" );
356    
357                        if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() )
358                        {
359                            throw new ToolException( this.getMessage( "failedCreatingDirectory", new Object[]
360                                {
361                                    file.getParentFile().getAbsolutePath()
362                                } ) );
363    
364                        }
365    
366                        if ( this.isLoggable( Level.INFO ) )
367                        {
368                            this.log( Level.INFO, this.getMessage( "writing", new Object[]
369                                {
370                                    file.getCanonicalPath()
371                                } ), null );
372    
373                        }
374    
375                        OutputStream out = null;
376                        try
377                        {
378                            out = new FileOutputStream( file );
379                            p.store( out, GENERATOR_NAME + ' ' + GENERATOR_VERSION );
380                        }
381                        finally
382                        {
383                            if ( out != null )
384                            {
385                                out.close();
386                            }
387                        }
388    
389                        if ( this.getDefaultLocale().getLanguage().equalsIgnoreCase( language ) )
390                        {
391                            defProperties = p;
392                        }
393    
394                        fallbackProperties = p;
395                    }
396    
397                    if ( defProperties == null )
398                    {
399                        defProperties = fallbackProperties;
400                    }
401    
402                    if ( defProperties != null )
403                    {
404                        final File file = new File( resourcesDirectory, bundlePath + ".properties" );
405                        if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() )
406                        {
407                            throw new ToolException( this.getMessage( "failedCreatingDirectory", new Object[]
408                                {
409                                    file.getParentFile().getAbsolutePath()
410                                } ) );
411    
412                        }
413    
414                        if ( this.isLoggable( Level.INFO ) )
415                        {
416                            this.log( Level.INFO, this.getMessage( "writing", new Object[]
417                                {
418                                    file.getCanonicalPath()
419                                } ), null );
420    
421                        }
422    
423                        OutputStream out = null;
424                        try
425                        {
426                            out = new FileOutputStream( file );
427                            defProperties.store( out, GENERATOR_NAME + ' ' + GENERATOR_VERSION );
428                        }
429                        finally
430                        {
431                            if ( out != null )
432                            {
433                                out.close();
434                            }
435                        }
436                    }
437                }
438            }
439            catch ( final IOException e )
440            {
441                throw new ToolException( e );
442            }
443        }
444    
445        /**
446         * Gets the source code of the Java class for accessing the resource bundle of a given implementation.
447         *
448         * @param implementation The implementation to get the source code of.
449         *
450         * @return The source code of the Java class for accessing the resource bundle of {@code implementation}.
451         *
452         * @throws NullPointerException if {@code implementation} is {@code null}.
453         * @throws ToolException if getting the source code fails.
454         */
455        public String getResourceBundleSources( final Implementation implementation ) throws ToolException
456        {
457            if ( implementation == null )
458            {
459                throw new NullPointerException( "implementation" );
460            }
461    
462            try
463            {
464                final StringWriter writer = new StringWriter();
465                final VelocityContext ctx = this.getVelocityContext();
466                final Template template = this.getVelocityTemplate( BUNDLE_TEMPLATE );
467                ctx.put( "implementation", implementation );
468                ctx.put( "template", template );
469                template.merge( ctx, writer );
470                writer.close();
471                return writer.toString();
472            }
473            catch ( final IOException e )
474            {
475                throw new ToolException( e );
476            }
477        }
478    
479        /**
480         * Gets the resource bundle properties of a given implementation.
481         *
482         * @param implementation The implementation to get resource bundle properties of.
483         *
484         * @return Resource bundle properties of {@code implementation}.
485         *
486         * @throws NullPointerException if {@code implementation} is {@code null}.
487         * @throws ToolException if getting the resources fails.
488         */
489        public Map<Locale, Properties> getResourceBundleResources( final Implementation implementation )
490            throws ToolException
491        {
492            if ( implementation == null )
493            {
494                throw new NullPointerException( "implementation" );
495            }
496    
497            final Map<Locale, java.util.Properties> properties = new HashMap<Locale, java.util.Properties>( 10 );
498            final Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
499    
500            if ( messages != null )
501            {
502                for ( Message message : messages.getMessage() )
503                {
504                    if ( message.getTemplate() != null )
505                    {
506                        for ( Text text : message.getTemplate().getText() )
507                        {
508                            final Locale locale = new Locale( text.getLanguage().toLowerCase() );
509                            Properties bundleProperties = properties.get( locale );
510    
511                            if ( bundleProperties == null )
512                            {
513                                bundleProperties = new Properties();
514                                properties.put( locale, bundleProperties );
515                            }
516    
517                            bundleProperties.setProperty( message.getName(), text.getValue() );
518                        }
519                    }
520                }
521            }
522    
523            return properties;
524        }
525    
526        /**
527         * Gets the velocity context used for merging templates.
528         *
529         * @return The velocity context used for merging templates.
530         */
531        @Override
532        public VelocityContext getVelocityContext()
533        {
534            final VelocityContext ctx = super.getVelocityContext();
535            ctx.put( "classSuffix", BUNDLE_SUFFIX );
536            ctx.put( "comment", Boolean.TRUE );
537            return ctx;
538        }
539    
540        private void assertValidTemplates( final Implementation implementation )
541        {
542            if ( implementation == null )
543            {
544                throw new NullPointerException( "implementation" );
545            }
546    
547            final Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
548            if ( messages != null )
549            {
550                for ( Message m : messages.getMessage() )
551                {
552                    if ( m.getTemplate() != null )
553                    {
554                        for ( Text t : m.getTemplate().getText() )
555                        {
556                            new MessageFormat( t.getValue() );
557                        }
558                    }
559                }
560            }
561        }
562    
563        private String getMessage( final String key, final Object args )
564        {
565            final ResourceBundle b = ResourceBundle.getBundle( JavaBundles.class.getName().replace( '.', '/' ) );
566            return new MessageFormat( b.getString( key ) ).format( args );
567        }
568    
569    }