001/*
002 *   Copyright (C) 2005 Christian Schulte <cs@schulte.it>
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 5307 2016-08-30 22:09:18Z schulte $
029 *
030 */
031package org.jomc.tools;
032
033import java.io.Closeable;
034import java.io.File;
035import java.io.FileOutputStream;
036import java.io.IOException;
037import java.lang.reflect.UndeclaredThrowableException;
038import java.nio.channels.FileLock;
039import java.text.MessageFormat;
040import java.util.HashMap;
041import java.util.LinkedList;
042import java.util.List;
043import java.util.Locale;
044import java.util.Map;
045import java.util.Properties;
046import java.util.ResourceBundle;
047import java.util.concurrent.Callable;
048import java.util.concurrent.CancellationException;
049import java.util.concurrent.ExecutionException;
050import java.util.concurrent.Future;
051import java.util.logging.Level;
052import org.apache.velocity.VelocityContext;
053import org.jomc.model.Implementation;
054import org.jomc.model.Implementations;
055import org.jomc.model.JavaTypeName;
056import org.jomc.model.Message;
057import org.jomc.model.Messages;
058import org.jomc.model.ModelObjectException;
059import org.jomc.model.Module;
060import org.jomc.model.Specification;
061import org.jomc.model.Specifications;
062import org.jomc.model.Text;
063
064/**
065 * Processes resource files.
066 *
067 * <p>
068 * <b>Use Cases:</b><br/><ul>
069 * <li>{@link #writeResourceBundleResourceFiles(File) }</li>
070 * <li>{@link #writeResourceBundleResourceFiles(Module, File) }</li>
071 * <li>{@link #writeResourceBundleResourceFiles(Specification, File) }</li>
072 * <li>{@link #writeResourceBundleResourceFiles(Implementation, File) }</li>
073 * </ul></p>
074 *
075 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
076 * @version $JOMC: ResourceFileProcessor.java 5307 2016-08-30 22:09:18Z schulte $
077 *
078 * @see #getModules()
079 */
080public class ResourceFileProcessor extends JomcTool
081{
082
083    /**
084     * The language of the default language properties file of generated resource bundle resources.
085     */
086    private volatile Locale resourceBundleDefaultLocale;
087
088    /**
089     * Creates a new {@code ResourceFileProcessor} instance.
090     */
091    public ResourceFileProcessor()
092    {
093        super();
094    }
095
096    /**
097     * Creates a new {@code ResourceFileProcessor} instance taking a {@code ResourceFileProcessor} instance to
098     * initialize the instance with.
099     *
100     * @param tool The instance to initialize the new instance with.
101     *
102     * @throws NullPointerException if {@code tool} is {@code null}.
103     * @throws IOException if copying {@code tool} fails.
104     */
105    public ResourceFileProcessor( final ResourceFileProcessor tool ) throws IOException
106    {
107        super( tool );
108        this.resourceBundleDefaultLocale = tool.resourceBundleDefaultLocale;
109    }
110
111    /**
112     * Gets the language of the default language properties file of generated resource bundle resource files.
113     *
114     * @return The language of the default language properties file of generated resource bundle resource files.
115     *
116     * @see #setResourceBundleDefaultLocale(java.util.Locale)
117     */
118    public final Locale getResourceBundleDefaultLocale()
119    {
120        if ( this.resourceBundleDefaultLocale == null )
121        {
122            this.resourceBundleDefaultLocale = Locale.ENGLISH;
123
124            if ( this.isLoggable( Level.CONFIG ) )
125            {
126                this.log( Level.CONFIG, getMessage( "defaultResourceBundleDefaultLocale",
127                                                    this.resourceBundleDefaultLocale ), null );
128
129            }
130        }
131
132        return this.resourceBundleDefaultLocale;
133    }
134
135    /**
136     * Sets the language of the default language properties file of generated resource bundle resource files.
137     *
138     * @param value The language of the default language properties file of generated resource bundle resource files.
139     *
140     * @see #getResourceBundleDefaultLocale()
141     */
142    public final void setResourceBundleDefaultLocale( final Locale value )
143    {
144        this.resourceBundleDefaultLocale = value;
145    }
146
147    /**
148     * Writes resource bundle resource files of the modules of the instance to a given directory.
149     *
150     * @param resourcesDirectory The directory to write resource bundle resource files to.
151     *
152     * @throws NullPointerException if {@code resourcesDirectory} is {@code null}.
153     * @throws IOException if writing resource bundle resource files fails.
154     * @throws ModelObjectException if compiling the name of a referenced type fails.
155     *
156     * @see #writeResourceBundleResourceFiles(org.jomc.model.Module, java.io.File)
157     */
158    public void writeResourceBundleResourceFiles( final File resourcesDirectory )
159        throws IOException, ModelObjectException
160    {
161        if ( resourcesDirectory == null )
162        {
163            throw new NullPointerException( "resourcesDirectory" );
164        }
165
166        if ( this.getModules() != null )
167        {
168            this.writeResourceBundleResourceFiles( this.getModules().getSpecifications(),
169                                                   this.getModules().getImplementations(),
170                                                   resourcesDirectory );
171
172        }
173        else if ( this.isLoggable( Level.WARNING ) )
174        {
175            this.log( Level.WARNING, getMessage( "modulesNotFound", this.getModel().getIdentifier() ), null );
176        }
177    }
178
179    /**
180     * Writes resource bundle resource files of a given module from the modules of the instance to a given directory.
181     *
182     * @param module The module to process.
183     * @param resourcesDirectory The directory to write resource bundle resource files to.
184     *
185     * @throws NullPointerException if {@code module} or {@code resourcesDirectory} is {@code null}.
186     * @throws IOException if writing resource bundle resource files fails.
187     * @throws ModelObjectException if compiling the name of a referenced type fails.
188     *
189     * @see #writeResourceBundleResourceFiles(org.jomc.model.Specification, java.io.File)
190     * @see #writeResourceBundleResourceFiles(org.jomc.model.Implementation, java.io.File)
191     */
192    public void writeResourceBundleResourceFiles( final Module module, final File resourcesDirectory )
193        throws IOException, ModelObjectException
194    {
195        if ( module == null )
196        {
197            throw new NullPointerException( "module" );
198        }
199        if ( resourcesDirectory == null )
200        {
201            throw new NullPointerException( "resourcesDirectory" );
202        }
203
204        if ( this.getModules() != null && this.getModules().getModule( module.getName() ) != null )
205        {
206            this.writeResourceBundleResourceFiles( module.getSpecifications(), module.getImplementations(),
207                                                   resourcesDirectory );
208
209        }
210        else if ( this.isLoggable( Level.WARNING ) )
211        {
212            this.log( Level.WARNING, getMessage( "moduleNotFound", module.getName() ), null );
213        }
214    }
215
216    /**
217     * Writes resource bundle resource files of a given specification from the modules of the instance to a directory.
218     *
219     * @param specification The specification to process.
220     * @param resourcesDirectory The directory to write resource bundle resource files to.
221     *
222     * @throws NullPointerException if {@code specification} or {@code resourcesDirectory} is {@code null}.
223     * @throws IOException if writing resource bundle resource files fails.
224     * @throws ModelObjectException if compiling the name of the type referenced by the specification fails.
225     *
226     * @see #getResourceBundleResources(org.jomc.model.Specification)
227     */
228    public void writeResourceBundleResourceFiles( final Specification specification, final File resourcesDirectory )
229        throws IOException, ModelObjectException
230    {
231        if ( specification == null )
232        {
233            throw new NullPointerException( "implementation" );
234        }
235        if ( resourcesDirectory == null )
236        {
237            throw new NullPointerException( "resourcesDirectory" );
238        }
239
240        if ( this.getModules() != null
241                 && this.getModules().getSpecification( specification.getIdentifier() ) != null )
242        {
243            if ( specification.isClassDeclaration() )
244            {
245                if ( !resourcesDirectory.isDirectory() )
246                {
247                    throw new IOException( getMessage( "directoryNotFound", resourcesDirectory.getAbsolutePath() ) );
248                }
249
250                this.assertValidTemplates( specification );
251
252                final JavaTypeName javaTypeName = specification.getJavaTypeName();
253
254                if ( javaTypeName != null )
255                {
256                    final String bundlePath = javaTypeName.getQualifiedName().replace( '.', File.separatorChar );
257                    this.writeResourceBundleResourceFiles(
258                        this.getResourceBundleResources( specification ), resourcesDirectory, bundlePath );
259
260                }
261            }
262        }
263        else if ( this.isLoggable( Level.WARNING ) )
264        {
265            this.log( Level.WARNING, getMessage( "specificationNotFound", specification.getIdentifier() ), null );
266        }
267    }
268
269    /**
270     * Writes resource bundle resource files of a given implementation from the modules of the instance to a directory.
271     *
272     * @param implementation The implementation to process.
273     * @param resourcesDirectory The directory to write resource bundle resource files to.
274     *
275     * @throws NullPointerException if {@code implementation} or {@code resourcesDirectory} is {@code null}.
276     * @throws IOException if writing resource bundle resource files fails.
277     * @throws ModelObjectException if compiling the name of the type referenced by the implementation fails.
278     *
279     * @see #getResourceBundleResources(org.jomc.model.Implementation)
280     */
281    public void writeResourceBundleResourceFiles( final Implementation implementation, final File resourcesDirectory )
282        throws IOException, ModelObjectException
283    {
284        if ( implementation == null )
285        {
286            throw new NullPointerException( "implementation" );
287        }
288        if ( resourcesDirectory == null )
289        {
290            throw new NullPointerException( "resourcesDirectory" );
291        }
292
293        if ( this.getModules() != null
294                 && this.getModules().getImplementation( implementation.getIdentifier() ) != null )
295        {
296            if ( implementation.isClassDeclaration() )
297            {
298                if ( !resourcesDirectory.isDirectory() )
299                {
300                    throw new IOException( getMessage( "directoryNotFound", resourcesDirectory.getAbsolutePath() ) );
301                }
302
303                this.assertValidTemplates( implementation );
304
305                final JavaTypeName javaTypeName = implementation.getJavaTypeName();
306
307                if ( javaTypeName != null )
308                {
309                    final String bundlePath = javaTypeName.getQualifiedName().replace( '.', File.separatorChar );
310                    this.writeResourceBundleResourceFiles(
311                        this.getResourceBundleResources( implementation ), resourcesDirectory, bundlePath );
312
313                }
314            }
315        }
316        else if ( this.isLoggable( Level.WARNING ) )
317        {
318            this.log( Level.WARNING, getMessage( "implementationNotFound", implementation.getIdentifier() ), null );
319        }
320    }
321
322    /**
323     * Gets resource bundle properties resources of a given specification.
324     *
325     * @param specification The specification to get resource bundle properties resources of.
326     *
327     * @return Resource bundle properties resources of {@code specification} or {@code null}, if no model objects are
328     * found.
329     *
330     * @throws NullPointerException if {@code specification} is {@code null}.
331     * @throws IOException if getting the resource bundle properties resources fails.
332     */
333    public Map<Locale, Properties> getResourceBundleResources( final Specification specification )
334        throws IOException
335    {
336        if ( specification == null )
337        {
338            throw new NullPointerException( "specification" );
339        }
340
341        Map<Locale, Properties> properties = null;
342
343        if ( this.getModules() != null
344                 && this.getModules().getSpecification( specification.getIdentifier() ) != null )
345        {
346            properties = new HashMap<Locale, Properties>();
347        }
348        else if ( this.isLoggable( Level.WARNING ) )
349        {
350            this.log( Level.WARNING, getMessage( "specificationNotFound", specification.getIdentifier() ), null );
351        }
352
353        return properties;
354    }
355
356    /**
357     * Gets resource bundle properties resources of a given implementation.
358     *
359     * @param implementation The implementation to get resource bundle properties resources of.
360     *
361     * @return Resource bundle properties resources of {@code implementation} or {@code null}, if no model objects are
362     * found.
363     *
364     * @throws NullPointerException if {@code implementation} is {@code null}.
365     * @throws IOException if getting the resource bundle properties resources fails.
366     */
367    public Map<Locale, Properties> getResourceBundleResources( final Implementation implementation )
368        throws IOException
369    {
370        if ( implementation == null )
371        {
372            throw new NullPointerException( "implementation" );
373        }
374
375        Map<Locale, Properties> properties = null;
376
377        if ( this.getModules() != null
378                 && this.getModules().getImplementation( implementation.getIdentifier() ) != null )
379        {
380            properties = new HashMap<Locale, java.util.Properties>( 10 );
381            final Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
382
383            if ( messages != null )
384            {
385                for ( int i = 0, s0 = messages.getMessage().size(); i < s0; i++ )
386                {
387                    final Message message = messages.getMessage().get( i );
388
389                    if ( message.getTemplate() != null )
390                    {
391                        for ( int j = 0, s1 = message.getTemplate().getText().size(); j < s1; j++ )
392                        {
393                            final Text text = message.getTemplate().getText().get( j );
394                            final Locale locale = new Locale( text.getLanguage().toLowerCase() );
395                            Properties bundleProperties = properties.get( locale );
396
397                            if ( bundleProperties == null )
398                            {
399                                bundleProperties = new Properties();
400                                properties.put( locale, bundleProperties );
401                            }
402
403                            bundleProperties.setProperty( message.getName(), text.getValue() );
404                        }
405                    }
406                }
407            }
408        }
409        else if ( this.isLoggable( Level.WARNING ) )
410        {
411            this.log( Level.WARNING, getMessage( "implementationNotFound", implementation.getIdentifier() ), null );
412        }
413
414        return properties;
415    }
416
417    private void writeResourceBundleResourceFiles( final Specifications specifications,
418                                                   final Implementations implementations,
419                                                   final File resourcesDirectory )
420        throws IOException, ModelObjectException
421    {
422        try
423        {
424            class WriteResourceBundleResourceFilesTask implements Callable<Void>
425            {
426
427                final Specification specification;
428
429                final Implementation implementation;
430
431                WriteResourceBundleResourceFilesTask( final Specification specification,
432                                                      final Implementation implementation )
433                {
434                    super();
435                    this.specification = specification;
436                    this.implementation = implementation;
437                }
438
439                @Override
440                public Void call() throws IOException, ModelObjectException
441                {
442                    if ( this.specification != null )
443                    {
444                        writeResourceBundleResourceFiles( this.specification, resourcesDirectory );
445                    }
446                    if ( this.implementation != null )
447                    {
448                        writeResourceBundleResourceFiles( this.implementation, resourcesDirectory );
449                    }
450
451                    return null;
452                }
453
454            }
455
456            final List<WriteResourceBundleResourceFilesTask> tasks =
457                new LinkedList<WriteResourceBundleResourceFilesTask>();
458
459            if ( specifications != null )
460            {
461                for ( int i = 0, s0 = specifications.getSpecification().size(); i < s0; i++ )
462                {
463                    final Specification specification = specifications.getSpecification().get( i );
464                    tasks.add( new WriteResourceBundleResourceFilesTask( specification, null ) );
465
466                    if ( specification.isClassDeclaration() && specification.getJavaTypeName() != null )
467                    {
468                        if ( !resourcesDirectory.isDirectory() )
469                        {
470                            throw new IOException( getMessage( "directoryNotFound",
471                                                               resourcesDirectory.getAbsolutePath() ) );
472
473                        }
474
475                        final String bundlePath =
476                            specification.getJavaTypeName().getQualifiedName().replace( '.', File.separatorChar );
477
478                        final File bundleDirectory = new File( resourcesDirectory, bundlePath ).getParentFile();
479
480                        if ( !bundleDirectory.exists() && !bundleDirectory.mkdirs() )
481                        {
482                            throw new IOException( getMessage( "failedCreatingDirectory",
483                                                               bundleDirectory.getAbsolutePath() ) );
484
485                        }
486                    }
487                }
488            }
489
490            if ( implementations != null )
491            {
492                for ( int i = 0, s0 = implementations.getImplementation().size(); i < s0; i++ )
493                {
494                    final Implementation implementation = implementations.getImplementation().get( i );
495                    tasks.add( new WriteResourceBundleResourceFilesTask( null, implementation ) );
496
497                    if ( implementation.isClassDeclaration() && implementation.getJavaTypeName() != null )
498                    {
499                        if ( !resourcesDirectory.isDirectory() )
500                        {
501                            throw new IOException( getMessage( "directoryNotFound",
502                                                               resourcesDirectory.getAbsolutePath() ) );
503
504                        }
505
506                        final String bundlePath =
507                            implementation.getJavaTypeName().getQualifiedName().replace( '.', File.separatorChar );
508
509                        final File bundleDirectory = new File( resourcesDirectory, bundlePath ).getParentFile();
510
511                        if ( !bundleDirectory.exists() && !bundleDirectory.mkdirs() )
512                        {
513                            throw new IOException( getMessage( "failedCreatingDirectory",
514                                                               bundleDirectory.getAbsolutePath() ) );
515
516                        }
517                    }
518                }
519            }
520
521            if ( this.getExecutorService() != null && tasks.size() > 1 )
522            {
523                for ( final Future<Void> task : this.getExecutorService().invokeAll( tasks ) )
524                {
525                    task.get();
526                }
527            }
528            else
529            {
530                for ( int i = 0, s0 = tasks.size(); i < s0; i++ )
531                {
532                    tasks.get( i ).call();
533                }
534            }
535        }
536        catch ( final CancellationException e )
537        {
538            // JDK: As of JDK6 new IOException( getMessage( e.getCause() ), e.getCause() )
539            throw (IOException) new IOException( getMessage( e.getCause() ) ).initCause( e.getCause() );
540        }
541        catch ( final InterruptedException e )
542        {
543            // JDK: As of JDK6 new IOException( getMessage( e.getCause() ), e.getCause() )
544            throw (IOException) new IOException( getMessage( e.getCause() ) ).initCause( e.getCause() );
545        }
546        catch ( final ExecutionException e )
547        {
548            if ( e.getCause() instanceof ModelObjectException )
549            {
550                throw (ModelObjectException) e.getCause();
551            }
552            else if ( e.getCause() instanceof IOException )
553            {
554                throw (IOException) e.getCause();
555            }
556            else if ( e.getCause() instanceof RuntimeException )
557            {
558                // The fork-join framework breaks the exception handling contract of Callable by re-throwing any
559                // exception caught using a runtime exception.
560                if ( e.getCause().getCause() instanceof ModelObjectException )
561                {
562                    throw (ModelObjectException) e.getCause().getCause();
563                }
564                else if ( e.getCause().getCause() instanceof IOException )
565                {
566                    throw (IOException) e.getCause().getCause();
567                }
568                else if ( e.getCause().getCause() instanceof RuntimeException )
569                {
570                    throw (RuntimeException) e.getCause().getCause();
571                }
572                else if ( e.getCause().getCause() instanceof Error )
573                {
574                    throw (Error) e.getCause().getCause();
575                }
576                else if ( e.getCause().getCause() instanceof Exception )
577                {
578                    // Checked exception not declared to be thrown by the Callable's 'call' method.
579                    throw new UndeclaredThrowableException( e.getCause().getCause() );
580                }
581                else
582                {
583                    throw (RuntimeException) e.getCause();
584                }
585            }
586            else if ( e.getCause() instanceof Error )
587            {
588                throw (Error) e.getCause();
589            }
590            else
591            {
592                // Checked exception not declared to be thrown by the Callable's 'call' method.
593                throw new UndeclaredThrowableException( e.getCause() );
594            }
595        }
596    }
597
598    private void writeResourceBundleResourceFiles( final Map<Locale, Properties> resources,
599                                                   final File resourcesDirectory, final String bundlePath )
600        throws IOException
601    {
602        if ( resources == null )
603        {
604            throw new NullPointerException( "resources" );
605        }
606        if ( resourcesDirectory == null )
607        {
608            throw new NullPointerException( "resourcesDirectory" );
609        }
610        if ( bundlePath == null )
611        {
612            throw new NullPointerException( "bundlePath" );
613        }
614
615        Properties defProperties = null;
616        Properties fallbackProperties = null;
617
618        final VelocityContext ctx = this.getVelocityContext();
619        final String toolName = ctx.get( "toolName" ).toString();
620        final String toolVersion = ctx.get( "toolVersion" ).toString();
621        final String toolUrl = ctx.get( "toolUrl" ).toString();
622
623        for ( final Map.Entry<Locale, Properties> e : resources.entrySet() )
624        {
625            final String language = e.getKey().getLanguage().toLowerCase();
626            final Properties p = e.getValue();
627            final File file = new File( resourcesDirectory, bundlePath + "_" + language + ".properties" );
628
629            if ( this.getResourceBundleDefaultLocale().getLanguage().equalsIgnoreCase( language ) )
630            {
631                defProperties = p;
632            }
633
634            fallbackProperties = p;
635
636            if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() )
637            {
638                throw new IOException( getMessage( "failedCreatingDirectory",
639                                                   file.getParentFile().getAbsolutePath() ) );
640
641            }
642
643            if ( this.isLoggable( Level.INFO ) )
644            {
645                this.log( Level.INFO, getMessage( "writing", file.getCanonicalPath() ), null );
646            }
647
648            this.writePropertiesFile( p, toolName + ' ' + toolVersion + " - See " + toolUrl, file );
649        }
650
651        if ( defProperties == null )
652        {
653            defProperties = fallbackProperties;
654        }
655
656        if ( defProperties != null )
657        {
658            final File file = new File( resourcesDirectory, bundlePath + ".properties" );
659
660            if ( !file.getParentFile().exists() && !file.getParentFile().mkdirs() )
661            {
662                throw new IOException( getMessage( "failedCreatingDirectory",
663                                                   file.getParentFile().getAbsolutePath() ) );
664
665            }
666
667            if ( this.isLoggable( Level.INFO ) )
668            {
669                this.log( Level.INFO, getMessage( "writing", file.getCanonicalPath() ), null );
670            }
671
672            this.writePropertiesFile( defProperties, toolName + ' ' + toolVersion + " - See " + toolUrl, file );
673        }
674    }
675
676    private void assertValidTemplates( final Specification specification )
677    {
678        if ( specification == null )
679        {
680            throw new NullPointerException( "specification" );
681        }
682    }
683
684    private void assertValidTemplates( final Implementation implementation )
685    {
686        if ( implementation == null )
687        {
688            throw new NullPointerException( "implementation" );
689        }
690
691        final Messages messages = this.getModules().getMessages( implementation.getIdentifier() );
692
693        if ( messages != null )
694        {
695            for ( int i = messages.getMessage().size() - 1; i >= 0; i-- )
696            {
697                final Message m = messages.getMessage().get( i );
698
699                if ( m.getTemplate() != null )
700                {
701                    for ( int j = m.getTemplate().getText().size() - 1; j >= 0; j-- )
702                    {
703                        new MessageFormat( m.getTemplate().getText().get( j ).getValue() );
704                    }
705                }
706            }
707        }
708    }
709
710    private void writePropertiesFile( final Properties properties, final String comments, final File propertiesFile )
711        throws IOException
712    {
713        FileOutputStream out = null;
714        FileLock fileLock = null;
715
716        try
717        {
718            out = new FileOutputStream( propertiesFile );
719            fileLock = out.getChannel().lock();
720
721            properties.store( out, comments );
722            out.getChannel().force( true );
723
724            fileLock.release();
725            fileLock = null;
726
727            out.close();
728            out = null;
729        }
730        finally
731        {
732            this.releaseAndClose( fileLock, out );
733        }
734    }
735
736    private void releaseAndClose( final FileLock fileLock, final Closeable closeable )
737        throws IOException
738    {
739        try
740        {
741            if ( fileLock != null )
742            {
743                fileLock.release();
744            }
745        }
746        catch ( final IOException e )
747        {
748            this.log( Level.SEVERE, getMessage( e ), e );
749        }
750        finally
751        {
752            try
753            {
754                if ( closeable != null )
755                {
756                    closeable.close();
757                }
758            }
759            catch ( final IOException e )
760            {
761                this.log( Level.SEVERE, getMessage( e ), e );
762            }
763        }
764    }
765
766    void initDefaults()
767    {
768        super.initDefaults();
769        this.getResourceBundleDefaultLocale();
770    }
771
772    private static String getMessage( final Throwable t )
773    {
774        return t != null
775                   ? t.getMessage() != null && t.getMessage().trim().length() > 0
776                         ? t.getMessage()
777                         : getMessage( t.getCause() )
778                   : null;
779
780    }
781
782    private static String getMessage( final String key, final Object... arguments )
783    {
784        if ( key == null )
785        {
786            throw new NullPointerException( "key" );
787        }
788
789        return MessageFormat.format( ResourceBundle.getBundle(
790            ResourceFileProcessor.class.getName().replace( '.', '/' ) ).getString( key ), arguments );
791
792    }
793
794}