ResourceFileProcessor.java

  1. /*
  2.  *   Copyright (C) Christian Schulte <cs@schulte.it>, 2005-206
  3.  *   All rights reserved.
  4.  *
  5.  *   Redistribution and use in source and binary forms, with or without
  6.  *   modification, are permitted provided that the following conditions
  7.  *   are met:
  8.  *
  9.  *     o Redistributions of source code must retain the above copyright
  10.  *       notice, this list of conditions and the following disclaimer.
  11.  *
  12.  *     o Redistributions in binary form must reproduce the above copyright
  13.  *       notice, this list of conditions and the following disclaimer in
  14.  *       the documentation and/or other materials provided with the
  15.  *       distribution.
  16.  *
  17.  *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  18.  *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
  19.  *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
  20.  *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
  21.  *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  22.  *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  23.  *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  24.  *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  25.  *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  26.  *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27.  *
  28.  *   $JOMC: ResourceFileProcessor.java 5043 2015-05-27 07:03:39Z schulte $
  29.  *
  30.  */
  31. package org.jomc.tools;

  32. import java.io.ByteArrayOutputStream;
  33. import java.io.Closeable;
  34. import java.io.File;
  35. import java.io.IOException;
  36. import java.io.RandomAccessFile;
  37. import java.nio.ByteBuffer;
  38. import java.nio.channels.FileChannel;
  39. import java.nio.channels.FileLock;
  40. import java.text.MessageFormat;
  41. import java.util.HashMap;
  42. import java.util.Locale;
  43. import java.util.Map;
  44. import java.util.Properties;
  45. import java.util.ResourceBundle;
  46. import java.util.logging.Level;
  47. import org.apache.velocity.VelocityContext;
  48. import org.jomc.model.Implementation;
  49. import org.jomc.model.JavaTypeName;
  50. import org.jomc.model.Message;
  51. import org.jomc.model.Messages;
  52. import org.jomc.model.ModelObjectException;
  53. import org.jomc.model.Module;
  54. import org.jomc.model.Specification;
  55. import org.jomc.model.Text;

  56. /**
  57.  * Processes resource files.
  58.  *
  59.  * <p>
  60.  * <b>Use Cases:</b><br/><ul>
  61.  * <li>{@link #writeResourceBundleResourceFiles(File) }</li>
  62.  * <li>{@link #writeResourceBundleResourceFiles(Module, File) }</li>
  63.  * <li>{@link #writeResourceBundleResourceFiles(Specification, File) }</li>
  64.  * <li>{@link #writeResourceBundleResourceFiles(Implementation, File) }</li>
  65.  * </ul></p>
  66.  *
  67.  * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
  68.  * @version $JOMC: ResourceFileProcessor.java 5043 2015-05-27 07:03:39Z schulte $
  69.  *
  70.  * @see #getModules()
  71.  */
  72. public class ResourceFileProcessor extends JomcTool
  73. {

  74.     /**
  75.      * The language of the default language properties file of generated resource bundle resources.
  76.      */
  77.     private Locale resourceBundleDefaultLocale;

  78.     /**
  79.      * Creates a new {@code ResourceFileProcessor} instance.
  80.      */
  81.     public ResourceFileProcessor()
  82.     {
  83.         super();
  84.     }

  85.     /**
  86.      * Creates a new {@code ResourceFileProcessor} instance taking a {@code ResourceFileProcessor} instance to
  87.      * initialize the instance with.
  88.      *
  89.      * @param tool The instance to initialize the new instance with.
  90.      *
  91.      * @throws NullPointerException if {@code tool} is {@code null}.
  92.      * @throws IOException if copying {@code tool} fails.
  93.      */
  94.     public ResourceFileProcessor( final ResourceFileProcessor tool ) throws IOException
  95.     {
  96.         super( tool );
  97.         this.resourceBundleDefaultLocale = tool.resourceBundleDefaultLocale;
  98.     }

  99.     /**
  100.      * Gets the language of the default language properties file of generated resource bundle resource files.
  101.      *
  102.      * @return The language of the default language properties file of generated resource bundle resource files.
  103.      *
  104.      * @see #setResourceBundleDefaultLocale(java.util.Locale)
  105.      */
  106.     public final Locale getResourceBundleDefaultLocale()
  107.     {
  108.         if ( this.resourceBundleDefaultLocale == null )
  109.         {
  110.             this.resourceBundleDefaultLocale = Locale.ENGLISH;

  111.             if ( this.isLoggable( Level.CONFIG ) )
  112.             {
  113.                 this.log( Level.CONFIG, getMessage( "defaultResourceBundleDefaultLocale",
  114.                                                     this.resourceBundleDefaultLocale ), null );

  115.             }
  116.         }

  117.         return this.resourceBundleDefaultLocale;
  118.     }

  119.     /**
  120.      * Sets the language of the default language properties file of generated resource bundle resource files.
  121.      *
  122.      * @param value The language of the default language properties file of generated resource bundle resource files.
  123.      *
  124.      * @see #getResourceBundleDefaultLocale()
  125.      */
  126.     public final void setResourceBundleDefaultLocale( final Locale value )
  127.     {
  128.         this.resourceBundleDefaultLocale = value;
  129.     }

  130.     /**
  131.      * Writes resource bundle resource files of the modules of the instance to a given directory.
  132.      *
  133.      * @param resourcesDirectory The directory to write resource bundle resource files to.
  134.      *
  135.      * @throws NullPointerException if {@code resourcesDirectory} is {@code null}.
  136.      * @throws IOException if writing resource bundle resource files fails.
  137.      * @throws ModelObjectException if compiling the name of a referenced type fails.
  138.      *
  139.      * @see #writeResourceBundleResourceFiles(org.jomc.model.Module, java.io.File)
  140.      */
  141.     public void writeResourceBundleResourceFiles( final File resourcesDirectory )
  142.         throws IOException, ModelObjectException
  143.     {
  144.         if ( resourcesDirectory == null )
  145.         {
  146.             throw new NullPointerException( "resourcesDirectory" );
  147.         }

  148.         if ( this.getModules() != null )
  149.         {
  150.             for ( int i = 0, s0 = this.getModules().getModule().size(); i < s0; i++ )
  151.             {
  152.                 this.writeResourceBundleResourceFiles( this.getModules().getModule().get( i ), resourcesDirectory );
  153.             }
  154.         }
  155.         else if ( this.isLoggable( Level.WARNING ) )
  156.         {
  157.             this.log( Level.WARNING, getMessage( "modulesNotFound", this.getModel().getIdentifier() ), null );
  158.         }
  159.     }

  160.     /**
  161.      * Writes resource bundle resource files of a given module from the modules of the instance to a given directory.
  162.      *
  163.      * @param module The module to process.
  164.      * @param resourcesDirectory The directory to write resource bundle resource files to.
  165.      *
  166.      * @throws NullPointerException if {@code module} or {@code resourcesDirectory} is {@code null}.
  167.      * @throws IOException if writing resource bundle resource files fails.
  168.      * @throws ModelObjectException if compiling the name of a referenced type fails.
  169.      *
  170.      * @see #writeResourceBundleResourceFiles(org.jomc.model.Specification, java.io.File)
  171.      * @see #writeResourceBundleResourceFiles(org.jomc.model.Implementation, java.io.File)
  172.      */
  173.     public void writeResourceBundleResourceFiles( final Module module, final File resourcesDirectory )
  174.         throws IOException, ModelObjectException
  175.     {
  176.         if ( module == null )
  177.         {
  178.             throw new NullPointerException( "module" );
  179.         }
  180.         if ( resourcesDirectory == null )
  181.         {
  182.             throw new NullPointerException( "resourcesDirectory" );
  183.         }

  184.         if ( this.getModules() != null && this.getModules().getModule( module.getName() ) != null )
  185.         {
  186.             if ( module.getSpecifications() != null )
  187.             {
  188.                 for ( int i = 0, s0 = module.getSpecifications().getSpecification().size(); i < s0; i++ )
  189.                 {
  190.                     this.writeResourceBundleResourceFiles( module.getSpecifications().getSpecification().get( i ),
  191.                                                            resourcesDirectory );

  192.                 }
  193.             }

  194.             if ( module.getImplementations() != null )
  195.             {
  196.                 for ( int i = 0, s0 = module.getImplementations().getImplementation().size(); i < s0; i++ )
  197.                 {
  198.                     this.writeResourceBundleResourceFiles( module.getImplementations().getImplementation().get( i ),
  199.                                                            resourcesDirectory );

  200.                 }
  201.             }
  202.         }
  203.         else if ( this.isLoggable( Level.WARNING ) )
  204.         {
  205.             this.log( Level.WARNING, getMessage( "moduleNotFound", module.getName() ), null );
  206.         }
  207.     }

  208.     /**
  209.      * Writes resource bundle resource files of a given specification from the modules of the instance to a directory.
  210.      *
  211.      * @param specification The specification to process.
  212.      * @param resourcesDirectory The directory to write resource bundle resource files to.
  213.      *
  214.      * @throws NullPointerException if {@code specification} or {@code resourcesDirectory} is {@code null}.
  215.      * @throws IOException if writing resource bundle resource files fails.
  216.      * @throws ModelObjectException if compiling the name of the type referenced by the specification fails.
  217.      *
  218.      * @see #getResourceBundleResources(org.jomc.model.Specification)
  219.      */
  220.     public void writeResourceBundleResourceFiles( final Specification specification, final File resourcesDirectory )
  221.         throws IOException, ModelObjectException
  222.     {
  223.         if ( specification == null )
  224.         {
  225.             throw new NullPointerException( "implementation" );
  226.         }
  227.         if ( resourcesDirectory == null )
  228.         {
  229.             throw new NullPointerException( "resourcesDirectory" );
  230.         }

  231.         if ( this.getModules() != null
  232.                  && this.getModules().getSpecification( specification.getIdentifier() ) != null )
  233.         {
  234.             if ( specification.isClassDeclaration() )
  235.             {
  236.                 if ( !resourcesDirectory.isDirectory() )
  237.                 {
  238.                     throw new IOException( getMessage( "directoryNotFound", resourcesDirectory.getAbsolutePath() ) );
  239.                 }

  240.                 this.assertValidTemplates( specification );

  241.                 final JavaTypeName javaTypeName = specification.getJavaTypeName();

  242.                 if ( javaTypeName != null )
  243.                 {
  244.                     final String bundlePath = javaTypeName.getQualifiedName().replace( '.', File.separatorChar );
  245.                     this.writeResourceBundleResourceFiles(
  246.                         this.getResourceBundleResources( specification ), resourcesDirectory, bundlePath );

  247.                 }
  248.             }
  249.         }
  250.         else if ( this.isLoggable( Level.WARNING ) )
  251.         {
  252.             this.log( Level.WARNING, getMessage( "specificationNotFound", specification.getIdentifier() ), null );
  253.         }
  254.     }

  255.     /**
  256.      * Writes resource bundle resource files of a given implementation from the modules of the instance to a directory.
  257.      *
  258.      * @param implementation The implementation to process.
  259.      * @param resourcesDirectory The directory to write resource bundle resource files to.
  260.      *
  261.      * @throws NullPointerException if {@code implementation} or {@code resourcesDirectory} is {@code null}.
  262.      * @throws IOException if writing resource bundle resource files fails.
  263.      * @throws ModelObjectException if compiling the name of the type referenced by the implementation fails.
  264.      *
  265.      * @see #getResourceBundleResources(org.jomc.model.Implementation)
  266.      */
  267.     public void writeResourceBundleResourceFiles( final Implementation implementation, final File resourcesDirectory )
  268.         throws IOException, ModelObjectException
  269.     {
  270.         if ( implementation == null )
  271.         {
  272.             throw new NullPointerException( "implementation" );
  273.         }
  274.         if ( resourcesDirectory == null )
  275.         {
  276.             throw new NullPointerException( "resourcesDirectory" );
  277.         }

  278.         if ( this.getModules() != null
  279.                  && this.getModules().getImplementation( implementation.getIdentifier() ) != null )
  280.         {
  281.             if ( implementation.isClassDeclaration() )
  282.             {
  283.                 if ( !resourcesDirectory.isDirectory() )
  284.                 {
  285.                     throw new IOException( getMessage( "directoryNotFound", resourcesDirectory.getAbsolutePath() ) );
  286.                 }

  287.                 this.assertValidTemplates( implementation );

  288.                 final JavaTypeName javaTypeName = implementation.getJavaTypeName();

  289.                 if ( javaTypeName != null )
  290.                 {
  291.                     final String bundlePath = javaTypeName.getQualifiedName().replace( '.', File.separatorChar );
  292.                     this.writeResourceBundleResourceFiles(
  293.                         this.getResourceBundleResources( implementation ), resourcesDirectory, bundlePath );

  294.                 }
  295.             }
  296.         }
  297.         else if ( this.isLoggable( Level.WARNING ) )
  298.         {
  299.             this.log( Level.WARNING, getMessage( "implementationNotFound", implementation.getIdentifier() ), null );
  300.         }
  301.     }

  302.     /**
  303.      * Gets resource bundle properties resources of a given specification.
  304.      *
  305.      * @param specification The specification to get resource bundle properties resources of.
  306.      *
  307.      * @return Resource bundle properties resources of {@code specification} or {@code null}, if no model objects are
  308.      * found.
  309.      *
  310.      * @throws NullPointerException if {@code specification} is {@code null}.
  311.      * @throws IOException if getting the resource bundle properties resources fails.
  312.      */
  313.     public Map<Locale, Properties> getResourceBundleResources( final Specification specification )
  314.         throws IOException
  315.     {
  316.         if ( specification == null )
  317.         {
  318.             throw new NullPointerException( "specification" );
  319.         }

  320.         Map<Locale, Properties> properties = null;

  321.         if ( this.getModules() != null
  322.                  && this.getModules().getSpecification( specification.getIdentifier() ) != null )
  323.         {
  324.             properties = new HashMap<Locale, Properties>();
  325.         }
  326.         else if ( this.isLoggable( Level.WARNING ) )
  327.         {
  328.             this.log( Level.WARNING, getMessage( "specificationNotFound", specification.getIdentifier() ), null );
  329.         }

  330.         return properties;
  331.     }

  332.     /**
  333.      * Gets resource bundle properties resources of a given implementation.
  334.      *
  335.      * @param implementation The implementation to get resource bundle properties resources of.
  336.      *
  337.      * @return Resource bundle properties resources of {@code implementation} or {@code null}, if no model objects are
  338.      * found.
  339.      *
  340.      * @throws NullPointerException if {@code implementation} is {@code null}.
  341.      * @throws IOException if getting the resource bundle properties resources fails.
  342.      */
  343.     public Map<Locale, Properties> getResourceBundleResources( final Implementation implementation )
  344.         throws IOException
  345.     {
  346.         if ( implementation == null )
  347.         {
  348.             throw new NullPointerException( "implementation" );
  349.         }

  350.         Map<Locale, Properties> properties = null;

  351.         if ( this.getModules() != null
  352.                  && this.getModules().getImplementation( implementation.getIdentifier() ) != null )
  353.         {
  354.             properties = new HashMap<Locale, java.util.Properties>( 10 );
  355.             final Messages messages = this.getModules().getMessages( implementation.getIdentifier() );

  356.             if ( messages != null )
  357.             {
  358.                 for ( int i = 0, s0 = messages.getMessage().size(); i < s0; i++ )
  359.                 {
  360.                     final Message message = messages.getMessage().get( i );

  361.                     if ( message.getTemplate() != null )
  362.                     {
  363.                         for ( int j = 0, s1 = message.getTemplate().getText().size(); j < s1; j++ )
  364.                         {
  365.                             final Text text = message.getTemplate().getText().get( j );
  366.                             final Locale locale = new Locale( text.getLanguage().toLowerCase() );
  367.                             Properties bundleProperties = properties.get( locale );

  368.                             if ( bundleProperties == null )
  369.                             {
  370.                                 bundleProperties = new Properties();
  371.                                 properties.put( locale, bundleProperties );
  372.                             }

  373.                             bundleProperties.setProperty( message.getName(), text.getValue() );
  374.                         }
  375.                     }
  376.                 }
  377.             }
  378.         }
  379.         else if ( this.isLoggable( Level.WARNING ) )
  380.         {
  381.             this.log( Level.WARNING, getMessage( "implementationNotFound", implementation.getIdentifier() ), null );
  382.         }

  383.         return properties;
  384.     }

  385.     private void writeResourceBundleResourceFiles( final Map<Locale, Properties> resources,
  386.                                                    final File resourcesDirectory, final String bundlePath )
  387.         throws IOException
  388.     {
  389.         if ( resources == null )
  390.         {
  391.             throw new NullPointerException( "resources" );
  392.         }
  393.         if ( resourcesDirectory == null )
  394.         {
  395.             throw new NullPointerException( "resourcesDirectory" );
  396.         }
  397.         if ( bundlePath == null )
  398.         {
  399.             throw new NullPointerException( "bundlePath" );
  400.         }

  401.         Properties defProperties = null;
  402.         Properties fallbackProperties = null;

  403.         final VelocityContext ctx = this.getVelocityContext();
  404.         final String toolName = ctx.get( "toolName" ).toString();
  405.         final String toolVersion = ctx.get( "toolVersion" ).toString();
  406.         final String toolUrl = ctx.get( "toolUrl" ).toString();

  407.         for ( final Map.Entry<Locale, Properties> e : resources.entrySet() )
  408.         {
  409.             final String language = e.getKey().getLanguage().toLowerCase();
  410.             final Properties p = e.getValue();
  411.             final File file = new File( resourcesDirectory, bundlePath + "_" + language + ".properties" );

  412.             if ( this.getResourceBundleDefaultLocale().getLanguage().equalsIgnoreCase( language ) )
  413.             {
  414.                 defProperties = p;
  415.             }

  416.             fallbackProperties = p;

  417.             if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() )
  418.             {
  419.                 throw new IOException( getMessage( "failedCreatingDirectory",
  420.                                                    file.getParentFile().getAbsolutePath() ) );

  421.             }

  422.             if ( this.isLoggable( Level.INFO ) )
  423.             {
  424.                 this.log( Level.INFO, getMessage( "writing", file.getCanonicalPath() ), null );
  425.             }

  426.             this.writePropertiesFile( p, toolName + ' ' + toolVersion + " - See " + toolUrl, file );
  427.         }

  428.         if ( defProperties == null )
  429.         {
  430.             defProperties = fallbackProperties;
  431.         }

  432.         if ( defProperties != null )
  433.         {
  434.             final File file = new File( resourcesDirectory, bundlePath + ".properties" );

  435.             if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() )
  436.             {
  437.                 throw new IOException( getMessage( "failedCreatingDirectory",
  438.                                                    file.getParentFile().getAbsolutePath() ) );

  439.             }

  440.             if ( this.isLoggable( Level.INFO ) )
  441.             {
  442.                 this.log( Level.INFO, getMessage( "writing", file.getCanonicalPath() ), null );
  443.             }

  444.             this.writePropertiesFile( defProperties, toolName + ' ' + toolVersion + " - See " + toolUrl, file );
  445.         }
  446.     }

  447.     private void assertValidTemplates( final Specification specification )
  448.     {
  449.         if ( specification == null )
  450.         {
  451.             throw new NullPointerException( "specification" );
  452.         }
  453.     }

  454.     private void assertValidTemplates( final Implementation implementation )
  455.     {
  456.         if ( implementation == null )
  457.         {
  458.             throw new NullPointerException( "implementation" );
  459.         }

  460.         final Messages messages = this.getModules().getMessages( implementation.getIdentifier() );

  461.         if ( messages != null )
  462.         {
  463.             for ( int i = messages.getMessage().size() - 1; i >= 0; i-- )
  464.             {
  465.                 final Message m = messages.getMessage().get( i );

  466.                 if ( m.getTemplate() != null )
  467.                 {
  468.                     for ( int j = m.getTemplate().getText().size() - 1; j >= 0; j-- )
  469.                     {
  470.                         new MessageFormat( m.getTemplate().getText().get( j ).getValue() );
  471.                     }
  472.                 }
  473.             }
  474.         }
  475.     }

  476.     private void writePropertiesFile( final Properties properties, final String comments, final File propertiesFile )
  477.         throws IOException
  478.     {
  479.         RandomAccessFile randomAccessFile = null;
  480.         FileChannel fileChannel = null;
  481.         FileLock fileLock = null;
  482.         boolean suppressExceptionOnClose = true;

  483.         final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
  484.         properties.store( byteStream, comments );
  485.         byteStream.close();

  486.         final byte[] bytes = byteStream.toByteArray();

  487.         try
  488.         {
  489.             randomAccessFile = new RandomAccessFile( propertiesFile, "rw" );
  490.             fileChannel = randomAccessFile.getChannel();
  491.             fileLock = fileChannel.lock();
  492.             fileChannel.truncate( bytes.length );
  493.             fileChannel.position( 0L );
  494.             fileChannel.write( ByteBuffer.wrap( bytes ) );
  495.             fileChannel.force( true );
  496.             suppressExceptionOnClose = false;
  497.         }
  498.         finally
  499.         {
  500.             this.releaseAndClose( fileLock, fileChannel, randomAccessFile, suppressExceptionOnClose );
  501.         }
  502.     }

  503.     private void releaseAndClose( final FileLock fileLock, final FileChannel fileChannel,
  504.                                   final Closeable closeable, final boolean suppressExceptions )
  505.         throws IOException
  506.     {
  507.         try
  508.         {
  509.             if ( fileLock != null )
  510.             {
  511.                 fileLock.release();
  512.             }
  513.         }
  514.         catch ( final IOException e )
  515.         {
  516.             if ( suppressExceptions )
  517.             {
  518.                 this.log( Level.SEVERE, null, e );
  519.             }
  520.             else
  521.             {
  522.                 throw e;
  523.             }
  524.         }
  525.         finally
  526.         {
  527.             try
  528.             {
  529.                 if ( fileChannel != null )
  530.                 {
  531.                     fileChannel.close();
  532.                 }
  533.             }
  534.             catch ( final IOException e )
  535.             {
  536.                 if ( suppressExceptions )
  537.                 {
  538.                     this.log( Level.SEVERE, null, e );
  539.                 }
  540.                 else
  541.                 {
  542.                     throw e;
  543.                 }
  544.             }
  545.             finally
  546.             {
  547.                 try
  548.                 {
  549.                     if ( closeable != null )
  550.                     {
  551.                         closeable.close();
  552.                     }
  553.                 }
  554.                 catch ( final IOException e )
  555.                 {
  556.                     if ( suppressExceptions )
  557.                     {
  558.                         this.log( Level.SEVERE, null, e );
  559.                     }
  560.                     else
  561.                     {
  562.                         throw e;
  563.                     }
  564.                 }
  565.             }
  566.         }
  567.     }

  568.     private static String getMessage( final String key, final Object... arguments )
  569.     {
  570.         if ( key == null )
  571.         {
  572.             throw new NullPointerException( "key" );
  573.         }

  574.         return MessageFormat.format( ResourceBundle.getBundle(
  575.             ResourceFileProcessor.class.getName().replace( '.', '/' ) ).getString( key ), arguments );

  576.     }

  577. }