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 }