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 }