001/* 002 * Copyright (C) Christian Schulte, 2005-206 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without 006 * modification, are permitted provided that the following conditions 007 * are met: 008 * 009 * o Redistributions of source code must retain the above copyright 010 * notice, this list of conditions and the following disclaimer. 011 * 012 * o Redistributions in binary form must reproduce the above copyright 013 * notice, this list of conditions and the following disclaimer in 014 * the documentation and/or other materials provided with the 015 * distribution. 016 * 017 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 018 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 019 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 020 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, 021 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 022 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 023 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 024 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 025 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 026 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 027 * 028 * $JOMC: ResourceFileProcessor.java 4583 2012-06-03 01:29:11Z schulte2005 $ 029 * 030 */ 031package org.jomc.tools; 032 033import java.io.ByteArrayOutputStream; 034import java.io.Closeable; 035import java.io.File; 036import java.io.IOException; 037import java.io.RandomAccessFile; 038import java.nio.ByteBuffer; 039import java.nio.channels.FileChannel; 040import java.nio.channels.FileLock; 041import java.text.MessageFormat; 042import java.util.HashMap; 043import java.util.Locale; 044import java.util.Map; 045import java.util.Properties; 046import java.util.ResourceBundle; 047import java.util.logging.Level; 048import org.apache.velocity.VelocityContext; 049import org.jomc.model.Implementation; 050import org.jomc.model.Message; 051import org.jomc.model.Messages; 052import org.jomc.model.Module; 053import org.jomc.model.Specification; 054import org.jomc.model.Text; 055 056/** 057 * Processes resource files. 058 * 059 * <p><b>Use Cases:</b><br/><ul> 060 * <li>{@link #writeResourceBundleResourceFiles(File) }</li> 061 * <li>{@link #writeResourceBundleResourceFiles(Module, File) }</li> 062 * <li>{@link #writeResourceBundleResourceFiles(Specification, File) }</li> 063 * <li>{@link #writeResourceBundleResourceFiles(Implementation, File) }</li> 064 * </ul></p> 065 * 066 * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a> 067 * @version $JOMC: ResourceFileProcessor.java 4583 2012-06-03 01:29:11Z schulte2005 $ 068 * 069 * @see #getModules() 070 */ 071public class ResourceFileProcessor extends JomcTool 072{ 073 074 /** The language of the default language properties file of generated resource bundle resources. */ 075 private Locale resourceBundleDefaultLocale; 076 077 /** Creates a new {@code ResourceFileProcessor} instance. */ 078 public ResourceFileProcessor() 079 { 080 super(); 081 } 082 083 /** 084 * Creates a new {@code ResourceFileProcessor} instance taking a {@code ResourceFileProcessor} instance to 085 * initialize the instance with. 086 * 087 * @param tool The instance to initialize the new instance with. 088 * 089 * @throws NullPointerException if {@code tool} is {@code null}. 090 * @throws IOException if copying {@code tool} fails. 091 */ 092 public ResourceFileProcessor( final ResourceFileProcessor tool ) throws IOException 093 { 094 super( tool ); 095 this.resourceBundleDefaultLocale = tool.resourceBundleDefaultLocale; 096 } 097 098 /** 099 * Gets the language of the default language properties file of generated resource bundle resource files. 100 * 101 * @return The language of the default language properties file of generated resource bundle resource files. 102 * 103 * @see #setResourceBundleDefaultLocale(java.util.Locale) 104 */ 105 public final Locale getResourceBundleDefaultLocale() 106 { 107 if ( this.resourceBundleDefaultLocale == null ) 108 { 109 this.resourceBundleDefaultLocale = Locale.ENGLISH; 110 111 if ( this.isLoggable( Level.CONFIG ) ) 112 { 113 this.log( Level.CONFIG, getMessage( "defaultResourceBundleDefaultLocale", 114 this.resourceBundleDefaultLocale ), null ); 115 116 } 117 } 118 119 return this.resourceBundleDefaultLocale; 120 } 121 122 /** 123 * Sets the language of the default language properties file of generated resource bundle resource files. 124 * 125 * @param value The language of the default language properties file of generated resource bundle resource files. 126 * 127 * @see #getResourceBundleDefaultLocale() 128 */ 129 public final void setResourceBundleDefaultLocale( final Locale value ) 130 { 131 this.resourceBundleDefaultLocale = value; 132 } 133 134 /** 135 * Writes resource bundle resource files of the modules of the instance to a given directory. 136 * 137 * @param resourcesDirectory The directory to write resource bundle resource files to. 138 * 139 * @throws NullPointerException if {@code resourcesDirectory} is {@code null}. 140 * @throws IOException if writing resource bundle resource files fails. 141 * 142 * @see #writeResourceBundleResourceFiles(org.jomc.model.Module, java.io.File) 143 */ 144 public void writeResourceBundleResourceFiles( final File resourcesDirectory ) throws IOException 145 { 146 if ( resourcesDirectory == null ) 147 { 148 throw new NullPointerException( "resourcesDirectory" ); 149 } 150 151 if ( this.getModules() != null ) 152 { 153 for ( int i = 0, s0 = this.getModules().getModule().size(); i < s0; i++ ) 154 { 155 this.writeResourceBundleResourceFiles( this.getModules().getModule().get( i ), resourcesDirectory ); 156 } 157 } 158 else if ( this.isLoggable( Level.WARNING ) ) 159 { 160 this.log( Level.WARNING, getMessage( "modulesNotFound", this.getModel().getIdentifier() ), null ); 161 } 162 } 163 164 /** 165 * Writes resource bundle resource files of a given module from the modules of the instance to a given directory. 166 * 167 * @param module The module to process. 168 * @param resourcesDirectory The directory to write resource bundle resource files to. 169 * 170 * @throws NullPointerException if {@code module} or {@code resourcesDirectory} is {@code null}. 171 * @throws IOException if writing resource bundle resource files fails. 172 * 173 * @see #writeResourceBundleResourceFiles(org.jomc.model.Specification, java.io.File) 174 * @see #writeResourceBundleResourceFiles(org.jomc.model.Implementation, java.io.File) 175 */ 176 public void writeResourceBundleResourceFiles( final Module module, final File resourcesDirectory ) 177 throws IOException 178 { 179 if ( module == null ) 180 { 181 throw new NullPointerException( "module" ); 182 } 183 if ( resourcesDirectory == null ) 184 { 185 throw new NullPointerException( "resourcesDirectory" ); 186 } 187 188 if ( this.getModules() != null && this.getModules().getModule( module.getName() ) != null ) 189 { 190 if ( module.getSpecifications() != null ) 191 { 192 for ( int i = 0, s0 = module.getSpecifications().getSpecification().size(); i < s0; i++ ) 193 { 194 this.writeResourceBundleResourceFiles( module.getSpecifications().getSpecification().get( i ), 195 resourcesDirectory ); 196 197 } 198 } 199 200 if ( module.getImplementations() != null ) 201 { 202 for ( int i = 0, s0 = module.getImplementations().getImplementation().size(); i < s0; i++ ) 203 { 204 this.writeResourceBundleResourceFiles( module.getImplementations().getImplementation().get( i ), 205 resourcesDirectory ); 206 207 } 208 } 209 } 210 else if ( this.isLoggable( Level.WARNING ) ) 211 { 212 this.log( Level.WARNING, getMessage( "moduleNotFound", module.getName() ), null ); 213 } 214 } 215 216 /** 217 * Writes resource bundle resource files of a given specification from the modules of the instance to a directory. 218 * 219 * @param specification The specification to process. 220 * @param resourcesDirectory The directory to write resource bundle resource files to. 221 * 222 * @throws NullPointerException if {@code specification} or {@code resourcesDirectory} is {@code null}. 223 * @throws IOException if writing resource bundle resource files fails. 224 * 225 * @see #getResourceBundleResources(org.jomc.model.Specification) 226 */ 227 public void writeResourceBundleResourceFiles( final Specification specification, final File resourcesDirectory ) 228 throws IOException 229 { 230 if ( specification == null ) 231 { 232 throw new NullPointerException( "implementation" ); 233 } 234 if ( resourcesDirectory == null ) 235 { 236 throw new NullPointerException( "resourcesDirectory" ); 237 } 238 239 if ( this.getModules() != null 240 && this.getModules().getSpecification( specification.getIdentifier() ) != null ) 241 { 242 if ( specification.isClassDeclaration() ) 243 { 244 if ( !resourcesDirectory.isDirectory() ) 245 { 246 throw new IOException( getMessage( "directoryNotFound", resourcesDirectory.getAbsolutePath() ) ); 247 } 248 249 this.assertValidTemplates( specification ); 250 251 final String bundlePath = 252 this.getJavaTypeName( specification, true ).replace( '.', File.separatorChar ); 253 254 this.writeResourceBundleResourceFiles( 255 this.getResourceBundleResources( specification ), resourcesDirectory, bundlePath ); 256 257 } 258 } 259 else if ( this.isLoggable( Level.WARNING ) ) 260 { 261 this.log( Level.WARNING, getMessage( "specificationNotFound", specification.getIdentifier() ), null ); 262 } 263 } 264 265 /** 266 * Writes resource bundle resource files of a given implementation from the modules of the instance to a directory. 267 * 268 * @param implementation The implementation to process. 269 * @param resourcesDirectory The directory to write resource bundle resource files to. 270 * 271 * @throws NullPointerException if {@code implementation} or {@code resourcesDirectory} is {@code null}. 272 * @throws IOException if writing resource bundle resource files fails. 273 * 274 * @see #getResourceBundleResources(org.jomc.model.Implementation) 275 */ 276 public void writeResourceBundleResourceFiles( final Implementation implementation, final File resourcesDirectory ) 277 throws IOException 278 { 279 if ( implementation == null ) 280 { 281 throw new NullPointerException( "implementation" ); 282 } 283 if ( resourcesDirectory == null ) 284 { 285 throw new NullPointerException( "resourcesDirectory" ); 286 } 287 288 if ( this.getModules() != null 289 && this.getModules().getImplementation( implementation.getIdentifier() ) != null ) 290 { 291 if ( implementation.isClassDeclaration() ) 292 { 293 if ( !resourcesDirectory.isDirectory() ) 294 { 295 throw new IOException( getMessage( "directoryNotFound", resourcesDirectory.getAbsolutePath() ) ); 296 } 297 298 this.assertValidTemplates( implementation ); 299 300 final String bundlePath = 301 this.getJavaTypeName( implementation, true ).replace( '.', File.separatorChar ); 302 303 this.writeResourceBundleResourceFiles( 304 this.getResourceBundleResources( implementation ), resourcesDirectory, bundlePath ); 305 306 } 307 } 308 else if ( this.isLoggable( Level.WARNING ) ) 309 { 310 this.log( Level.WARNING, getMessage( "implementationNotFound", implementation.getIdentifier() ), null ); 311 } 312 } 313 314 /** 315 * Gets resource bundle properties resources of a given specification. 316 * 317 * @param specification The specification to get resource bundle properties resources of. 318 * 319 * @return Resource bundle properties resources of {@code specification} or {@code null}, if no model objects are 320 * found. 321 * 322 * @throws NullPointerException if {@code specification} is {@code null}. 323 * @throws IOException if getting the resource bundle properties resources fails. 324 */ 325 public Map<Locale, Properties> getResourceBundleResources( final Specification specification ) 326 throws IOException 327 { 328 if ( specification == null ) 329 { 330 throw new NullPointerException( "specification" ); 331 } 332 333 Map<Locale, Properties> properties = null; 334 335 if ( this.getModules() != null 336 && this.getModules().getSpecification( specification.getIdentifier() ) != null ) 337 { 338 properties = new HashMap<Locale, Properties>(); 339 } 340 else if ( this.isLoggable( Level.WARNING ) ) 341 { 342 this.log( Level.WARNING, getMessage( "specificationNotFound", specification.getIdentifier() ), null ); 343 } 344 345 return properties; 346 } 347 348 /** 349 * Gets resource bundle properties resources of a given implementation. 350 * 351 * @param implementation The implementation to get resource bundle properties resources of. 352 * 353 * @return Resource bundle properties resources of {@code implementation} or {@code null}, if no model objects are 354 * found. 355 * 356 * @throws NullPointerException if {@code implementation} is {@code null}. 357 * @throws IOException if getting the resource bundle properties resources fails. 358 */ 359 public Map<Locale, Properties> getResourceBundleResources( final Implementation implementation ) 360 throws IOException 361 { 362 if ( implementation == null ) 363 { 364 throw new NullPointerException( "implementation" ); 365 } 366 367 Map<Locale, Properties> properties = null; 368 369 if ( this.getModules() != null 370 && this.getModules().getImplementation( implementation.getIdentifier() ) != null ) 371 { 372 properties = new HashMap<Locale, java.util.Properties>( 10 ); 373 final Messages messages = this.getModules().getMessages( implementation.getIdentifier() ); 374 375 if ( messages != null ) 376 { 377 for ( int i = 0, s0 = messages.getMessage().size(); i < s0; i++ ) 378 { 379 final Message message = messages.getMessage().get( i ); 380 381 if ( message.getTemplate() != null ) 382 { 383 for ( int j = 0, s1 = message.getTemplate().getText().size(); j < s1; j++ ) 384 { 385 final Text text = message.getTemplate().getText().get( j ); 386 final Locale locale = new Locale( text.getLanguage().toLowerCase() ); 387 Properties bundleProperties = properties.get( locale ); 388 389 if ( bundleProperties == null ) 390 { 391 bundleProperties = new Properties(); 392 properties.put( locale, bundleProperties ); 393 } 394 395 bundleProperties.setProperty( message.getName(), text.getValue() ); 396 } 397 } 398 } 399 } 400 } 401 else if ( this.isLoggable( Level.WARNING ) ) 402 { 403 this.log( Level.WARNING, getMessage( "implementationNotFound", implementation.getIdentifier() ), null ); 404 } 405 406 return properties; 407 } 408 409 private void writeResourceBundleResourceFiles( final Map<Locale, Properties> resources, 410 final File resourcesDirectory, final String bundlePath ) 411 throws IOException 412 { 413 if ( resources == null ) 414 { 415 throw new NullPointerException( "resources" ); 416 } 417 if ( resourcesDirectory == null ) 418 { 419 throw new NullPointerException( "resourcesDirectory" ); 420 } 421 if ( bundlePath == null ) 422 { 423 throw new NullPointerException( "bundlePath" ); 424 } 425 426 Properties defProperties = null; 427 Properties fallbackProperties = null; 428 429 final VelocityContext ctx = this.getVelocityContext(); 430 final String toolName = ctx.get( "toolName" ).toString(); 431 final String toolVersion = ctx.get( "toolVersion" ).toString(); 432 final String toolUrl = ctx.get( "toolUrl" ).toString(); 433 434 for ( Map.Entry<Locale, Properties> e : resources.entrySet() ) 435 { 436 final String language = e.getKey().getLanguage().toLowerCase(); 437 final Properties p = e.getValue(); 438 final File file = new File( resourcesDirectory, bundlePath + "_" + language + ".properties" ); 439 440 if ( this.getResourceBundleDefaultLocale().getLanguage().equalsIgnoreCase( language ) ) 441 { 442 defProperties = p; 443 } 444 445 fallbackProperties = p; 446 447 if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() ) 448 { 449 throw new IOException( getMessage( "failedCreatingDirectory", 450 file.getParentFile().getAbsolutePath() ) ); 451 452 } 453 454 if ( this.isLoggable( Level.INFO ) ) 455 { 456 this.log( Level.INFO, getMessage( "writing", file.getCanonicalPath() ), null ); 457 } 458 459 this.writePropertiesFile( p, toolName + ' ' + toolVersion + " - See " + toolUrl, file ); 460 } 461 462 if ( defProperties == null ) 463 { 464 defProperties = fallbackProperties; 465 } 466 467 if ( defProperties != null ) 468 { 469 final File file = new File( resourcesDirectory, bundlePath + ".properties" ); 470 471 if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() ) 472 { 473 throw new IOException( getMessage( "failedCreatingDirectory", 474 file.getParentFile().getAbsolutePath() ) ); 475 476 } 477 478 if ( this.isLoggable( Level.INFO ) ) 479 { 480 this.log( Level.INFO, getMessage( "writing", file.getCanonicalPath() ), null ); 481 } 482 483 this.writePropertiesFile( defProperties, toolName + ' ' + toolVersion + " - See " + toolUrl, file ); 484 } 485 } 486 487 private void assertValidTemplates( final Specification specification ) 488 { 489 if ( specification == null ) 490 { 491 throw new NullPointerException( "specification" ); 492 } 493 } 494 495 private void assertValidTemplates( final Implementation implementation ) 496 { 497 if ( implementation == null ) 498 { 499 throw new NullPointerException( "implementation" ); 500 } 501 502 final Messages messages = this.getModules().getMessages( implementation.getIdentifier() ); 503 504 if ( messages != null ) 505 { 506 for ( int i = messages.getMessage().size() - 1; i >= 0; i-- ) 507 { 508 final Message m = messages.getMessage().get( i ); 509 510 if ( m.getTemplate() != null ) 511 { 512 for ( int j = m.getTemplate().getText().size() - 1; j >= 0; j-- ) 513 { 514 new MessageFormat( m.getTemplate().getText().get( j ).getValue() ); 515 } 516 } 517 } 518 } 519 } 520 521 private void writePropertiesFile( final Properties properties, final String comments, final File propertiesFile ) 522 throws IOException 523 { 524 RandomAccessFile randomAccessFile = null; 525 FileChannel fileChannel = null; 526 FileLock fileLock = null; 527 boolean suppressExceptionOnClose = true; 528 529 final ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); 530 properties.store( byteStream, comments ); 531 byteStream.close(); 532 533 final byte[] bytes = byteStream.toByteArray(); 534 535 try 536 { 537 randomAccessFile = new RandomAccessFile( propertiesFile, "rw" ); 538 fileChannel = randomAccessFile.getChannel(); 539 fileLock = fileChannel.lock(); 540 fileChannel.truncate( bytes.length ); 541 fileChannel.position( 0L ); 542 fileChannel.write( ByteBuffer.wrap( bytes ) ); 543 fileChannel.force( true ); 544 suppressExceptionOnClose = false; 545 } 546 finally 547 { 548 this.releaseAndClose( fileLock, fileChannel, randomAccessFile, suppressExceptionOnClose ); 549 } 550 } 551 552 private void releaseAndClose( final FileLock fileLock, final FileChannel fileChannel, 553 final Closeable closeable, final boolean suppressExceptions ) 554 throws IOException 555 { 556 try 557 { 558 if ( fileLock != null ) 559 { 560 fileLock.release(); 561 } 562 } 563 catch ( final IOException e ) 564 { 565 if ( suppressExceptions ) 566 { 567 this.log( Level.SEVERE, null, e ); 568 } 569 else 570 { 571 throw e; 572 } 573 } 574 finally 575 { 576 try 577 { 578 if ( fileChannel != null ) 579 { 580 fileChannel.close(); 581 } 582 } 583 catch ( final IOException e ) 584 { 585 if ( suppressExceptions ) 586 { 587 this.log( Level.SEVERE, null, e ); 588 } 589 else 590 { 591 throw e; 592 } 593 } 594 finally 595 { 596 try 597 { 598 if ( closeable != null ) 599 { 600 closeable.close(); 601 } 602 } 603 catch ( final IOException e ) 604 { 605 if ( suppressExceptions ) 606 { 607 this.log( Level.SEVERE, null, e ); 608 } 609 else 610 { 611 throw e; 612 } 613 } 614 } 615 } 616 } 617 618 private static String getMessage( final String key, final Object... arguments ) 619 { 620 if ( key == null ) 621 { 622 throw new NullPointerException( "key" ); 623 } 624 625 return MessageFormat.format( ResourceBundle.getBundle( 626 ResourceFileProcessor.class.getName().replace( '.', '/' ) ).getString( key ), arguments ); 627 628 } 629 630 private static String getMessage( final Throwable t ) 631 { 632 return t != null ? t.getMessage() != null ? t.getMessage() : getMessage( t.getCause() ) : null; 633 } 634 635}