ResourceFileProcessor.java
/*
* Copyright (C) 2005 Christian Schulte <cs@schulte.it>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* o Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* $JOMC: ResourceFileProcessor.java 5307 2016-08-30 22:09:18Z schulte $
*
*/
package org.jomc.tools;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.UndeclaredThrowableException;
import java.nio.channels.FileLock;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Level;
import org.apache.velocity.VelocityContext;
import org.jomc.model.Implementation;
import org.jomc.model.Implementations;
import org.jomc.model.JavaTypeName;
import org.jomc.model.Message;
import org.jomc.model.Messages;
import org.jomc.model.ModelObjectException;
import org.jomc.model.Module;
import org.jomc.model.Specification;
import org.jomc.model.Specifications;
import org.jomc.model.Text;
/**
* Processes resource files.
*
* <p>
* <b>Use Cases:</b><br/><ul>
* <li>{@link #writeResourceBundleResourceFiles(File) }</li>
* <li>{@link #writeResourceBundleResourceFiles(Module, File) }</li>
* <li>{@link #writeResourceBundleResourceFiles(Specification, File) }</li>
* <li>{@link #writeResourceBundleResourceFiles(Implementation, File) }</li>
* </ul></p>
*
* @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
* @version $JOMC: ResourceFileProcessor.java 5307 2016-08-30 22:09:18Z schulte $
*
* @see #getModules()
*/
public class ResourceFileProcessor extends JomcTool
{
/**
* The language of the default language properties file of generated resource bundle resources.
*/
private volatile Locale resourceBundleDefaultLocale;
/**
* Creates a new {@code ResourceFileProcessor} instance.
*/
public ResourceFileProcessor()
{
super();
}
/**
* Creates a new {@code ResourceFileProcessor} instance taking a {@code ResourceFileProcessor} instance to
* initialize the instance with.
*
* @param tool The instance to initialize the new instance with.
*
* @throws NullPointerException if {@code tool} is {@code null}.
* @throws IOException if copying {@code tool} fails.
*/
public ResourceFileProcessor( final ResourceFileProcessor tool ) throws IOException
{
super( tool );
this.resourceBundleDefaultLocale = tool.resourceBundleDefaultLocale;
}
/**
* Gets the language of the default language properties file of generated resource bundle resource files.
*
* @return The language of the default language properties file of generated resource bundle resource files.
*
* @see #setResourceBundleDefaultLocale(java.util.Locale)
*/
public final Locale getResourceBundleDefaultLocale()
{
if ( this.resourceBundleDefaultLocale == null )
{
this.resourceBundleDefaultLocale = Locale.ENGLISH;
if ( this.isLoggable( Level.CONFIG ) )
{
this.log( Level.CONFIG, getMessage( "defaultResourceBundleDefaultLocale",
this.resourceBundleDefaultLocale ), null );
}
}
return this.resourceBundleDefaultLocale;
}
/**
* Sets the language of the default language properties file of generated resource bundle resource files.
*
* @param value The language of the default language properties file of generated resource bundle resource files.
*
* @see #getResourceBundleDefaultLocale()
*/
public final void setResourceBundleDefaultLocale( final Locale value )
{
this.resourceBundleDefaultLocale = value;
}
/**
* Writes resource bundle resource files of the modules of the instance to a given directory.
*
* @param resourcesDirectory The directory to write resource bundle resource files to.
*
* @throws NullPointerException if {@code resourcesDirectory} is {@code null}.
* @throws IOException if writing resource bundle resource files fails.
* @throws ModelObjectException if compiling the name of a referenced type fails.
*
* @see #writeResourceBundleResourceFiles(org.jomc.model.Module, java.io.File)
*/
public void writeResourceBundleResourceFiles( final File resourcesDirectory )
throws IOException, ModelObjectException
{
if ( resourcesDirectory == null )
{
throw new NullPointerException( "resourcesDirectory" );
}
if ( this.getModules() != null )
{
this.writeResourceBundleResourceFiles( this.getModules().getSpecifications(),
this.getModules().getImplementations(),
resourcesDirectory );
}
else if ( this.isLoggable( Level.WARNING ) )
{
this.log( Level.WARNING, getMessage( "modulesNotFound", this.getModel().getIdentifier() ), null );
}
}
/**
* Writes resource bundle resource files of a given module from the modules of the instance to a given directory.
*
* @param module The module to process.
* @param resourcesDirectory The directory to write resource bundle resource files to.
*
* @throws NullPointerException if {@code module} or {@code resourcesDirectory} is {@code null}.
* @throws IOException if writing resource bundle resource files fails.
* @throws ModelObjectException if compiling the name of a referenced type fails.
*
* @see #writeResourceBundleResourceFiles(org.jomc.model.Specification, java.io.File)
* @see #writeResourceBundleResourceFiles(org.jomc.model.Implementation, java.io.File)
*/
public void writeResourceBundleResourceFiles( final Module module, final File resourcesDirectory )
throws IOException, ModelObjectException
{
if ( module == null )
{
throw new NullPointerException( "module" );
}
if ( resourcesDirectory == null )
{
throw new NullPointerException( "resourcesDirectory" );
}
if ( this.getModules() != null && this.getModules().getModule( module.getName() ) != null )
{
this.writeResourceBundleResourceFiles( module.getSpecifications(), module.getImplementations(),
resourcesDirectory );
}
else if ( this.isLoggable( Level.WARNING ) )
{
this.log( Level.WARNING, getMessage( "moduleNotFound", module.getName() ), null );
}
}
/**
* Writes resource bundle resource files of a given specification from the modules of the instance to a directory.
*
* @param specification The specification to process.
* @param resourcesDirectory The directory to write resource bundle resource files to.
*
* @throws NullPointerException if {@code specification} or {@code resourcesDirectory} is {@code null}.
* @throws IOException if writing resource bundle resource files fails.
* @throws ModelObjectException if compiling the name of the type referenced by the specification fails.
*
* @see #getResourceBundleResources(org.jomc.model.Specification)
*/
public void writeResourceBundleResourceFiles( final Specification specification, final File resourcesDirectory )
throws IOException, ModelObjectException
{
if ( specification == null )
{
throw new NullPointerException( "implementation" );
}
if ( resourcesDirectory == null )
{
throw new NullPointerException( "resourcesDirectory" );
}
if ( this.getModules() != null
&& this.getModules().getSpecification( specification.getIdentifier() ) != null )
{
if ( specification.isClassDeclaration() )
{
if ( !resourcesDirectory.isDirectory() )
{
throw new IOException( getMessage( "directoryNotFound", resourcesDirectory.getAbsolutePath() ) );
}
this.assertValidTemplates( specification );
final JavaTypeName javaTypeName = specification.getJavaTypeName();
if ( javaTypeName != null )
{
final String bundlePath = javaTypeName.getQualifiedName().replace( '.', File.separatorChar );
this.writeResourceBundleResourceFiles(
this.getResourceBundleResources( specification ), resourcesDirectory, bundlePath );
}
}
}
else if ( this.isLoggable( Level.WARNING ) )
{
this.log( Level.WARNING, getMessage( "specificationNotFound", specification.getIdentifier() ), null );
}
}
/**
* Writes resource bundle resource files of a given implementation from the modules of the instance to a directory.
*
* @param implementation The implementation to process.
* @param resourcesDirectory The directory to write resource bundle resource files to.
*
* @throws NullPointerException if {@code implementation} or {@code resourcesDirectory} is {@code null}.
* @throws IOException if writing resource bundle resource files fails.
* @throws ModelObjectException if compiling the name of the type referenced by the implementation fails.
*
* @see #getResourceBundleResources(org.jomc.model.Implementation)
*/
public void writeResourceBundleResourceFiles( final Implementation implementation, final File resourcesDirectory )
throws IOException, ModelObjectException
{
if ( implementation == null )
{
throw new NullPointerException( "implementation" );
}
if ( resourcesDirectory == null )
{
throw new NullPointerException( "resourcesDirectory" );
}
if ( this.getModules() != null
&& this.getModules().getImplementation( implementation.getIdentifier() ) != null )
{
if ( implementation.isClassDeclaration() )
{
if ( !resourcesDirectory.isDirectory() )
{
throw new IOException( getMessage( "directoryNotFound", resourcesDirectory.getAbsolutePath() ) );
}
this.assertValidTemplates( implementation );
final JavaTypeName javaTypeName = implementation.getJavaTypeName();
if ( javaTypeName != null )
{
final String bundlePath = javaTypeName.getQualifiedName().replace( '.', File.separatorChar );
this.writeResourceBundleResourceFiles(
this.getResourceBundleResources( implementation ), resourcesDirectory, bundlePath );
}
}
}
else if ( this.isLoggable( Level.WARNING ) )
{
this.log( Level.WARNING, getMessage( "implementationNotFound", implementation.getIdentifier() ), null );
}
}
/**
* Gets resource bundle properties resources of a given specification.
*
* @param specification The specification to get resource bundle properties resources of.
*
* @return Resource bundle properties resources of {@code specification} or {@code null}, if no model objects are
* found.
*
* @throws NullPointerException if {@code specification} is {@code null}.
* @throws IOException if getting the resource bundle properties resources fails.
*/
public Map<Locale, Properties> getResourceBundleResources( final Specification specification )
throws IOException
{
if ( specification == null )
{
throw new NullPointerException( "specification" );
}
Map<Locale, Properties> properties = null;
if ( this.getModules() != null
&& this.getModules().getSpecification( specification.getIdentifier() ) != null )
{
properties = new HashMap<Locale, Properties>();
}
else if ( this.isLoggable( Level.WARNING ) )
{
this.log( Level.WARNING, getMessage( "specificationNotFound", specification.getIdentifier() ), null );
}
return properties;
}
/**
* Gets resource bundle properties resources of a given implementation.
*
* @param implementation The implementation to get resource bundle properties resources of.
*
* @return Resource bundle properties resources of {@code implementation} or {@code null}, if no model objects are
* found.
*
* @throws NullPointerException if {@code implementation} is {@code null}.
* @throws IOException if getting the resource bundle properties resources fails.
*/
public Map<Locale, Properties> getResourceBundleResources( final Implementation implementation )
throws IOException
{
if ( implementation == null )
{
throw new NullPointerException( "implementation" );
}
Map<Locale, Properties> properties = null;
if ( this.getModules() != null
&& this.getModules().getImplementation( implementation.getIdentifier() ) != null )
{
properties = new HashMap<Locale, java.util.Properties>( 10 );
final Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
if ( messages != null )
{
for ( int i = 0, s0 = messages.getMessage().size(); i < s0; i++ )
{
final Message message = messages.getMessage().get( i );
if ( message.getTemplate() != null )
{
for ( int j = 0, s1 = message.getTemplate().getText().size(); j < s1; j++ )
{
final Text text = message.getTemplate().getText().get( j );
final Locale locale = new Locale( text.getLanguage().toLowerCase() );
Properties bundleProperties = properties.get( locale );
if ( bundleProperties == null )
{
bundleProperties = new Properties();
properties.put( locale, bundleProperties );
}
bundleProperties.setProperty( message.getName(), text.getValue() );
}
}
}
}
}
else if ( this.isLoggable( Level.WARNING ) )
{
this.log( Level.WARNING, getMessage( "implementationNotFound", implementation.getIdentifier() ), null );
}
return properties;
}
private void writeResourceBundleResourceFiles( final Specifications specifications,
final Implementations implementations,
final File resourcesDirectory )
throws IOException, ModelObjectException
{
try
{
class WriteResourceBundleResourceFilesTask implements Callable<Void>
{
final Specification specification;
final Implementation implementation;
WriteResourceBundleResourceFilesTask( final Specification specification,
final Implementation implementation )
{
super();
this.specification = specification;
this.implementation = implementation;
}
@Override
public Void call() throws IOException, ModelObjectException
{
if ( this.specification != null )
{
writeResourceBundleResourceFiles( this.specification, resourcesDirectory );
}
if ( this.implementation != null )
{
writeResourceBundleResourceFiles( this.implementation, resourcesDirectory );
}
return null;
}
}
final List<WriteResourceBundleResourceFilesTask> tasks =
new LinkedList<WriteResourceBundleResourceFilesTask>();
if ( specifications != null )
{
for ( int i = 0, s0 = specifications.getSpecification().size(); i < s0; i++ )
{
final Specification specification = specifications.getSpecification().get( i );
tasks.add( new WriteResourceBundleResourceFilesTask( specification, null ) );
if ( specification.isClassDeclaration() && specification.getJavaTypeName() != null )
{
if ( !resourcesDirectory.isDirectory() )
{
throw new IOException( getMessage( "directoryNotFound",
resourcesDirectory.getAbsolutePath() ) );
}
final String bundlePath =
specification.getJavaTypeName().getQualifiedName().replace( '.', File.separatorChar );
final File bundleDirectory = new File( resourcesDirectory, bundlePath ).getParentFile();
if ( !bundleDirectory.exists() && !bundleDirectory.mkdirs() )
{
throw new IOException( getMessage( "failedCreatingDirectory",
bundleDirectory.getAbsolutePath() ) );
}
}
}
}
if ( implementations != null )
{
for ( int i = 0, s0 = implementations.getImplementation().size(); i < s0; i++ )
{
final Implementation implementation = implementations.getImplementation().get( i );
tasks.add( new WriteResourceBundleResourceFilesTask( null, implementation ) );
if ( implementation.isClassDeclaration() && implementation.getJavaTypeName() != null )
{
if ( !resourcesDirectory.isDirectory() )
{
throw new IOException( getMessage( "directoryNotFound",
resourcesDirectory.getAbsolutePath() ) );
}
final String bundlePath =
implementation.getJavaTypeName().getQualifiedName().replace( '.', File.separatorChar );
final File bundleDirectory = new File( resourcesDirectory, bundlePath ).getParentFile();
if ( !bundleDirectory.exists() && !bundleDirectory.mkdirs() )
{
throw new IOException( getMessage( "failedCreatingDirectory",
bundleDirectory.getAbsolutePath() ) );
}
}
}
}
if ( this.getExecutorService() != null && tasks.size() > 1 )
{
for ( final Future<Void> task : this.getExecutorService().invokeAll( tasks ) )
{
task.get();
}
}
else
{
for ( int i = 0, s0 = tasks.size(); i < s0; i++ )
{
tasks.get( i ).call();
}
}
}
catch ( final CancellationException e )
{
// JDK: As of JDK6 new IOException( getMessage( e.getCause() ), e.getCause() )
throw (IOException) new IOException( getMessage( e.getCause() ) ).initCause( e.getCause() );
}
catch ( final InterruptedException e )
{
// JDK: As of JDK6 new IOException( getMessage( e.getCause() ), e.getCause() )
throw (IOException) new IOException( getMessage( e.getCause() ) ).initCause( e.getCause() );
}
catch ( final ExecutionException e )
{
if ( e.getCause() instanceof ModelObjectException )
{
throw (ModelObjectException) e.getCause();
}
else if ( e.getCause() instanceof IOException )
{
throw (IOException) e.getCause();
}
else if ( e.getCause() instanceof RuntimeException )
{
// The fork-join framework breaks the exception handling contract of Callable by re-throwing any
// exception caught using a runtime exception.
if ( e.getCause().getCause() instanceof ModelObjectException )
{
throw (ModelObjectException) e.getCause().getCause();
}
else if ( e.getCause().getCause() instanceof IOException )
{
throw (IOException) e.getCause().getCause();
}
else if ( e.getCause().getCause() instanceof RuntimeException )
{
throw (RuntimeException) e.getCause().getCause();
}
else if ( e.getCause().getCause() instanceof Error )
{
throw (Error) e.getCause().getCause();
}
else if ( e.getCause().getCause() instanceof Exception )
{
// Checked exception not declared to be thrown by the Callable's 'call' method.
throw new UndeclaredThrowableException( e.getCause().getCause() );
}
else
{
throw (RuntimeException) e.getCause();
}
}
else if ( e.getCause() instanceof Error )
{
throw (Error) e.getCause();
}
else
{
// Checked exception not declared to be thrown by the Callable's 'call' method.
throw new UndeclaredThrowableException( e.getCause() );
}
}
}
private void writeResourceBundleResourceFiles( final Map<Locale, Properties> resources,
final File resourcesDirectory, final String bundlePath )
throws IOException
{
if ( resources == null )
{
throw new NullPointerException( "resources" );
}
if ( resourcesDirectory == null )
{
throw new NullPointerException( "resourcesDirectory" );
}
if ( bundlePath == null )
{
throw new NullPointerException( "bundlePath" );
}
Properties defProperties = null;
Properties fallbackProperties = null;
final VelocityContext ctx = this.getVelocityContext();
final String toolName = ctx.get( "toolName" ).toString();
final String toolVersion = ctx.get( "toolVersion" ).toString();
final String toolUrl = ctx.get( "toolUrl" ).toString();
for ( final Map.Entry<Locale, Properties> e : resources.entrySet() )
{
final String language = e.getKey().getLanguage().toLowerCase();
final Properties p = e.getValue();
final File file = new File( resourcesDirectory, bundlePath + "_" + language + ".properties" );
if ( this.getResourceBundleDefaultLocale().getLanguage().equalsIgnoreCase( language ) )
{
defProperties = p;
}
fallbackProperties = p;
if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() )
{
throw new IOException( getMessage( "failedCreatingDirectory",
file.getParentFile().getAbsolutePath() ) );
}
if ( this.isLoggable( Level.INFO ) )
{
this.log( Level.INFO, getMessage( "writing", file.getCanonicalPath() ), null );
}
this.writePropertiesFile( p, toolName + ' ' + toolVersion + " - See " + toolUrl, file );
}
if ( defProperties == null )
{
defProperties = fallbackProperties;
}
if ( defProperties != null )
{
final File file = new File( resourcesDirectory, bundlePath + ".properties" );
if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() )
{
throw new IOException( getMessage( "failedCreatingDirectory",
file.getParentFile().getAbsolutePath() ) );
}
if ( this.isLoggable( Level.INFO ) )
{
this.log( Level.INFO, getMessage( "writing", file.getCanonicalPath() ), null );
}
this.writePropertiesFile( defProperties, toolName + ' ' + toolVersion + " - See " + toolUrl, file );
}
}
private void assertValidTemplates( final Specification specification )
{
if ( specification == null )
{
throw new NullPointerException( "specification" );
}
}
private void assertValidTemplates( final Implementation implementation )
{
if ( implementation == null )
{
throw new NullPointerException( "implementation" );
}
final Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
if ( messages != null )
{
for ( int i = messages.getMessage().size() - 1; i >= 0; i-- )
{
final Message m = messages.getMessage().get( i );
if ( m.getTemplate() != null )
{
for ( int j = m.getTemplate().getText().size() - 1; j >= 0; j-- )
{
new MessageFormat( m.getTemplate().getText().get( j ).getValue() );
}
}
}
}
}
private void writePropertiesFile( final Properties properties, final String comments, final File propertiesFile )
throws IOException
{
FileOutputStream out = null;
FileLock fileLock = null;
try
{
out = new FileOutputStream( propertiesFile );
fileLock = out.getChannel().lock();
properties.store( out, comments );
out.getChannel().force( true );
fileLock.release();
fileLock = null;
out.close();
out = null;
}
finally
{
this.releaseAndClose( fileLock, out );
}
}
private void releaseAndClose( final FileLock fileLock, final Closeable closeable )
throws IOException
{
try
{
if ( fileLock != null )
{
fileLock.release();
}
}
catch ( final IOException e )
{
this.log( Level.SEVERE, getMessage( e ), e );
}
finally
{
try
{
if ( closeable != null )
{
closeable.close();
}
}
catch ( final IOException e )
{
this.log( Level.SEVERE, getMessage( e ), e );
}
}
}
void initDefaults()
{
super.initDefaults();
this.getResourceBundleDefaultLocale();
}
private static String getMessage( final Throwable t )
{
return t != null
? t.getMessage() != null && t.getMessage().trim().length() > 0
? t.getMessage()
: getMessage( t.getCause() )
: null;
}
private static String getMessage( final String key, final Object... arguments )
{
if ( key == null )
{
throw new NullPointerException( "key" );
}
return MessageFormat.format( ResourceBundle.getBundle(
ResourceFileProcessor.class.getName().replace( '.', '/' ) ).getString( key ), arguments );
}
}