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 }