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