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 }