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