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: JomcTool.java 5307 2016-08-30 22:09:18Z schulte $
029 *
030 */
031package org.jomc.tools;
032
033import java.io.BufferedReader;
034import java.io.ByteArrayInputStream;
035import java.io.ByteArrayOutputStream;
036import java.io.Closeable;
037import java.io.FileNotFoundException;
038import java.io.IOException;
039import java.io.InputStream;
040import java.io.InputStreamReader;
041import java.io.OutputStreamWriter;
042import java.io.StringReader;
043import java.lang.ref.Reference;
044import java.lang.ref.SoftReference;
045import java.lang.reflect.InvocationTargetException;
046import java.net.URL;
047import java.text.DateFormat;
048import java.text.Format;
049import java.text.MessageFormat;
050import java.text.ParseException;
051import java.text.SimpleDateFormat;
052import java.util.ArrayList;
053import java.util.Calendar;
054import java.util.Collections;
055import java.util.Enumeration;
056import java.util.HashMap;
057import java.util.HashSet;
058import java.util.List;
059import java.util.Locale;
060import java.util.Map;
061import java.util.ResourceBundle;
062import java.util.Set;
063import java.util.concurrent.ConcurrentHashMap;
064import java.util.concurrent.CopyOnWriteArrayList;
065import java.util.concurrent.ExecutorService;
066import java.util.logging.Level;
067import javax.activation.MimeTypeParseException;
068import org.apache.commons.lang.StringEscapeUtils;
069import org.apache.commons.lang.StringUtils;
070import org.apache.velocity.Template;
071import org.apache.velocity.VelocityContext;
072import org.apache.velocity.app.VelocityEngine;
073import org.apache.velocity.exception.ParseErrorException;
074import org.apache.velocity.exception.ResourceNotFoundException;
075import org.apache.velocity.exception.VelocityException;
076import org.apache.velocity.runtime.RuntimeConstants;
077import org.apache.velocity.runtime.RuntimeServices;
078import org.apache.velocity.runtime.log.LogChute;
079import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
080import org.apache.velocity.runtime.resource.loader.URLResourceLoader;
081import org.jomc.model.Argument;
082import org.jomc.model.Dependency;
083import org.jomc.model.Implementation;
084import org.jomc.model.InheritanceModel;
085import org.jomc.model.JavaIdentifier;
086import org.jomc.model.JavaTypeName;
087import org.jomc.model.Message;
088import org.jomc.model.ModelObject;
089import org.jomc.model.ModelObjectException;
090import org.jomc.model.Modules;
091import org.jomc.model.Multiplicity;
092import org.jomc.model.Property;
093import org.jomc.model.Specification;
094import org.jomc.model.SpecificationReference;
095import org.jomc.model.Text;
096import org.jomc.model.Texts;
097import org.jomc.model.modlet.ModelHelper;
098import org.jomc.modlet.Model;
099
100/**
101 * Base tool class.
102 *
103 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
104 * @version $JOMC: JomcTool.java 5307 2016-08-30 22:09:18Z schulte $
105 */
106public class JomcTool
107{
108
109    /**
110     * Listener interface.
111     */
112    public abstract static class Listener
113    {
114
115        /**
116         * Creates a new {@code Listener} instance.
117         */
118        public Listener()
119        {
120            super();
121        }
122
123        /**
124         * Gets called on logging.
125         *
126         * @param level The level of the event.
127         * @param message The message of the event or {@code null}.
128         * @param throwable The throwable of the event or {@code null}.
129         *
130         * @throws NullPointerException if {@code level} is {@code null}.
131         */
132        public void onLog( final Level level, final String message, final Throwable throwable )
133        {
134            if ( level == null )
135            {
136                throw new NullPointerException( "level" );
137            }
138        }
139
140    }
141
142    /**
143     * Empty byte array.
144     */
145    private static final byte[] NO_BYTES =
146    {
147    };
148
149    /**
150     * The prefix of the template location.
151     */
152    private static final String TEMPLATE_PREFIX =
153        JomcTool.class.getPackage().getName().replace( '.', '/' ) + "/templates/";
154
155    /**
156     * Constant for the default template profile.
157     */
158    private static final String DEFAULT_TEMPLATE_PROFILE = "jomc-java";
159
160    /**
161     * Constant for the name of the template profile property specifying a parent template profile name.
162     *
163     * @since 1.3
164     */
165    private static final String PARENT_TEMPLATE_PROFILE_PROPERTY_NAME = "parent-template-profile";
166
167    /**
168     * Constant for the name of the template profile property specifying the template encoding.
169     *
170     * @since 1.3
171     */
172    private static final String TEMPLATE_ENCODING_PROFILE_PROPERTY_NAME = "template-encoding";
173
174    /**
175     * The default encoding to use for reading templates.
176     *
177     * @since 1.3
178     */
179    private volatile String defaultTemplateEncoding;
180
181    /**
182     * The default template profile.
183     */
184    private static volatile String defaultTemplateProfile;
185
186    /**
187     * The log level events are logged at by default.
188     *
189     * @see #getDefaultLogLevel()
190     */
191    private static final Level DEFAULT_LOG_LEVEL = Level.WARNING;
192
193    /**
194     * The default log level.
195     */
196    private static volatile Level defaultLogLevel;
197
198    /**
199     * The model of the instance.
200     */
201    private volatile Model model;
202
203    /**
204     * The {@code VelocityEngine} of the instance.
205     */
206    private volatile VelocityEngine velocityEngine;
207
208    /**
209     * Flag indicating the default {@code VelocityEngine}.
210     *
211     * @since 1.2.4
212     */
213    private volatile boolean defaultVelocityEngine;
214
215    /**
216     * The location to search for templates in addition to searching the class path.
217     *
218     * @since 1.2
219     */
220    private volatile URL templateLocation;
221
222    /**
223     * The encoding to use for reading files.
224     */
225    private volatile String inputEncoding;
226
227    /**
228     * The encoding to use for writing files.
229     */
230    private volatile String outputEncoding;
231
232    /**
233     * The template parameters.
234     *
235     * @since 1.2
236     */
237    private final Map<String, Object> templateParameters =
238        Collections.synchronizedMap( new HashMap<String, Object>( 32 ) );
239    // ConcurrentHashMap does not allow for putting null values.
240
241    /**
242     * The template profile of the instance.
243     */
244    private volatile String templateProfile;
245
246    /**
247     * The indentation string of the instance.
248     */
249    private volatile String indentation;
250
251    /**
252     * The line separator of the instance.
253     */
254    private volatile String lineSeparator;
255
256    /**
257     * The listeners of the instance.
258     */
259    private final List<Listener> listeners = new CopyOnWriteArrayList<Listener>();
260
261    /**
262     * The log level of the instance.
263     */
264    private volatile Level logLevel;
265
266    /**
267     * The locale of the instance.
268     *
269     * @since 1.2
270     */
271    private volatile Locale locale;
272
273    /**
274     * The {@code ExecutorService} of the instance.
275     *
276     * @since 1.10
277     */
278    private volatile ExecutorService executorService;
279
280    /**
281     * Cached indentation strings.
282     */
283    private volatile Reference<Map<String, String>> indentationCache;
284
285    /**
286     * Cached templates.
287     *
288     * @since 1.3
289     */
290    private volatile Reference<Map<String, TemplateData>> templateCache;
291
292    /**
293     * Cached template profile context properties.
294     *
295     * @since 1.3
296     */
297    private volatile Reference<Map<String, java.util.Properties>> templateProfileContextPropertiesCache;
298
299    /**
300     * Cached template profile properties.
301     *
302     * @since 1.3
303     */
304    private volatile Reference<Map<String, java.util.Properties>> templateProfilePropertiesCache;
305
306    /**
307     * Cached Java keywords.
308     */
309    private volatile Reference<Set<String>> javaKeywordsCache;
310
311    /**
312     * Creates a new {@code JomcTool} instance.
313     */
314    public JomcTool()
315    {
316        super();
317    }
318
319    /**
320     * Creates a new {@code JomcTool} instance taking a {@code JomcTool} instance to initialize the new instance with.
321     *
322     * @param tool The instance to initialize the new instance with.
323     *
324     * @throws NullPointerException if {@code tool} is {@code null}.
325     * @throws IOException if copying {@code tool} fails.
326     */
327    public JomcTool( final JomcTool tool ) throws IOException
328    {
329        this();
330
331        if ( tool == null )
332        {
333            throw new NullPointerException( "tool" );
334        }
335
336        this.indentation = tool.indentation;
337        this.inputEncoding = tool.inputEncoding;
338        this.lineSeparator = tool.lineSeparator;
339        this.listeners.addAll( tool.listeners );
340        this.logLevel = tool.logLevel;
341        this.model = tool.model != null ? tool.model.clone() : null;
342        this.outputEncoding = tool.outputEncoding;
343        this.defaultTemplateEncoding = tool.defaultTemplateEncoding;
344        this.templateProfile = tool.templateProfile;
345        this.velocityEngine = tool.velocityEngine;
346        this.defaultVelocityEngine = tool.defaultVelocityEngine;
347        this.locale = tool.locale;
348        this.templateParameters.putAll( tool.templateParameters );
349        this.templateLocation =
350            tool.templateLocation != null ? new URL( tool.templateLocation.toExternalForm() ) : null;
351
352        this.executorService = tool.executorService;
353    }
354
355    /**
356     * Gets the list of registered listeners.
357     * <p>
358     * This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
359     * to the returned list will be present inside the object. This is why there is no {@code set} method for the
360     * listeners property.
361     * </p>
362     *
363     * @return The list of registered listeners.
364     *
365     * @see #log(java.util.logging.Level, java.lang.String, java.lang.Throwable)
366     */
367    public List<Listener> getListeners()
368    {
369        return this.listeners;
370    }
371
372    /**
373     * Gets the default log level events are logged at.
374     * <p>
375     * The default log level is controlled by system property {@code org.jomc.tools.JomcTool.defaultLogLevel} holding
376     * the log level to log events at by default. If that property is not set, the {@code WARNING} default is
377     * returned.
378     * </p>
379     *
380     * @return The log level events are logged at by default.
381     *
382     * @see #getLogLevel()
383     * @see Level#parse(java.lang.String)
384     */
385    public static Level getDefaultLogLevel()
386    {
387        if ( defaultLogLevel == null )
388        {
389            defaultLogLevel = Level.parse( System.getProperty( "org.jomc.tools.JomcTool.defaultLogLevel",
390                                                               DEFAULT_LOG_LEVEL.getName() ) );
391
392        }
393
394        return defaultLogLevel;
395    }
396
397    /**
398     * Sets the default log level events are logged at.
399     *
400     * @param value The new default level events are logged at or {@code null}.
401     *
402     * @see #getDefaultLogLevel()
403     */
404    public static void setDefaultLogLevel( final Level value )
405    {
406        defaultLogLevel = value;
407    }
408
409    /**
410     * Gets the log level of the instance.
411     *
412     * @return The log level of the instance.
413     *
414     * @see #getDefaultLogLevel()
415     * @see #setLogLevel(java.util.logging.Level)
416     * @see #isLoggable(java.util.logging.Level)
417     */
418    public final Level getLogLevel()
419    {
420        if ( this.logLevel == null )
421        {
422            this.logLevel = getDefaultLogLevel();
423
424            if ( this.isLoggable( Level.CONFIG ) )
425            {
426                this.log( Level.CONFIG, getMessage( "defaultLogLevelInfo", this.logLevel.getLocalizedName() ), null );
427            }
428        }
429
430        return this.logLevel;
431    }
432
433    /**
434     * Sets the log level of the instance.
435     *
436     * @param value The new log level of the instance or {@code null}.
437     *
438     * @see #getLogLevel()
439     * @see #isLoggable(java.util.logging.Level)
440     */
441    public final void setLogLevel( final Level value )
442    {
443        this.logLevel = value;
444    }
445
446    /**
447     * Checks if a message at a given level is provided to the listeners of the instance.
448     *
449     * @param level The level to test.
450     *
451     * @return {@code true}, if messages at {@code level} are provided to the listeners of the instance;
452     * {@code false}, if messages at {@code level} are not provided to the listeners of the instance.
453     *
454     * @throws NullPointerException if {@code level} is {@code null}.
455     *
456     * @see #getLogLevel()
457     * @see #setLogLevel(java.util.logging.Level)
458     * @see #log(java.util.logging.Level, java.lang.String, java.lang.Throwable)
459     */
460    public boolean isLoggable( final Level level )
461    {
462        if ( level == null )
463        {
464            throw new NullPointerException( "level" );
465        }
466
467        return level.intValue() >= this.getLogLevel().intValue();
468    }
469
470    /**
471     * Gets the Java package name of a specification.
472     *
473     * @param specification The specification to get the Java package name of.
474     *
475     * @return The Java package name of {@code specification} or {@code null}, if the specification does not reference a
476     * type.
477     *
478     * @throws NullPointerException if {@code specification} is {@code null}.
479     * @throws ModelObjectException if compiling the name of the referenced type to a {@code JavaTypeName} fails.
480     *
481     * @see Specification#getJavaTypeName()
482     * @see JavaTypeName#getPackageName()
483     *
484     * @deprecated As of JOMC 1.4, please use method {@link Specification#getJavaTypeName()}. This method will be
485     * removed in JOMC 2.0.
486     */
487    @Deprecated
488    public String getJavaPackageName( final Specification specification ) throws ModelObjectException
489    {
490        if ( specification == null )
491        {
492            throw new NullPointerException( "specification" );
493        }
494
495        final JavaTypeName javaTypeName = specification.getJavaTypeName();
496        return javaTypeName != null ? javaTypeName.getPackageName() : null;
497    }
498
499    /**
500     * Gets the Java type name of a specification.
501     *
502     * @param specification The specification to get the Java type name of.
503     * @param qualified {@code true}, to return the fully qualified type name (with package name prepended);
504     * {@code false}, to return the short type name (without package name prepended).
505     *
506     * @return The Java type name of the type referenced by the specification or {@code null}, if the specification does
507     * not reference a type.
508     *
509     * @throws NullPointerException if {@code specification} is {@code null}.
510     * @throws ModelObjectException if compiling the name of the referenced type to a {@code JavaTypeName} fails.
511     *
512     * @see Specification#getJavaTypeName()
513     * @see JavaTypeName#getName(boolean)
514     *
515     * @deprecated As of JOMC 1.4, please use method {@link Specification#getJavaTypeName()}. This method will be
516     * removed in JOMC 2.0.
517     */
518    @Deprecated
519    public String getJavaTypeName( final Specification specification, final boolean qualified )
520        throws ModelObjectException
521    {
522        if ( specification == null )
523        {
524            throw new NullPointerException( "specification" );
525        }
526
527        final JavaTypeName javaTypeName = specification.getJavaTypeName();
528        return javaTypeName != null ? javaTypeName.getName( qualified ) : null;
529    }
530
531    /**
532     * Gets the Java class path location of a specification.
533     *
534     * @param specification The specification to return the Java class path location of.
535     *
536     * @return The Java class path location of {@code specification} or {@code null}, if the specification does not
537     * reference a type.
538     *
539     * @throws NullPointerException if {@code specification} is {@code null}.
540     * @throws ModelObjectException if compiling the name of the referenced type to a {@code JavaTypeName} fails.
541     *
542     * @see Specification#getJavaTypeName()
543     * @see JavaTypeName#getQualifiedName()
544     *
545     * @deprecated As of JOMC 1.4, please use method {@link Specification#getJavaTypeName()}. This method will be
546     * removed in JOMC 2.0.
547     */
548    @Deprecated
549    public String getJavaClasspathLocation( final Specification specification ) throws ModelObjectException
550    {
551        if ( specification == null )
552        {
553            throw new NullPointerException( "specification" );
554        }
555
556        final JavaTypeName javaTypeName = specification.getJavaTypeName();
557        return javaTypeName != null ? javaTypeName.getQualifiedName().replace( '.', '/' ) : null;
558    }
559
560    /**
561     * Gets the Java package name of a specification reference.
562     *
563     * @param reference The specification reference to get the Java package name of.
564     *
565     * @return The Java package name of {@code reference} or {@code null}, if the referenced specification is not found
566     * or does not reference a type.
567     *
568     * @throws NullPointerException if {@code reference} is {@code null}.
569     * @throws ModelObjectException if compiling the name of the referenced type to a {@code JavaTypeName} fails.
570     *
571     * @see Modules#getSpecification(java.lang.String)
572     * @see Specification#getJavaTypeName()
573     * @see JavaTypeName#getPackageName()
574     *
575     * @deprecated As of JOMC 1.4, please use method {@link Specification#getJavaTypeName()}. This method will be
576     * removed in JOMC 2.0.
577     */
578    @Deprecated
579    public String getJavaPackageName( final SpecificationReference reference ) throws ModelObjectException
580    {
581        if ( reference == null )
582        {
583            throw new NullPointerException( "reference" );
584        }
585
586        Specification s = null;
587        String javaPackageName = null;
588
589        if ( this.getModules() != null
590                 && ( s = this.getModules().getSpecification( reference.getIdentifier() ) ) != null )
591        {
592            final JavaTypeName javaTypeName = s.getJavaTypeName();
593            javaPackageName = javaTypeName != null ? javaTypeName.getPackageName() : null;
594        }
595        else if ( this.isLoggable( Level.WARNING ) )
596        {
597            this.log( Level.WARNING, getMessage( "specificationNotFound", reference.getIdentifier() ), null );
598        }
599
600        return javaPackageName;
601    }
602
603    /**
604     * Gets the name of a Java type of a given specification reference.
605     *
606     * @param reference The specification reference to get a Java type name of.
607     * @param qualified {@code true}, to return the fully qualified type name (with package name prepended);
608     * {@code false}, to return the short type name (without package name prepended).
609     *
610     * @return The Java type name of {@code reference} or {@code null}, if the referenced specification is not found
611     * or does not reference a type.
612     *
613     * @throws NullPointerException if {@code reference} is {@code null}.
614     * @throws ModelObjectException if compiling the name of the referenced type to a {@code JavaTypeName} fails.
615     *
616     * @see Modules#getSpecification(java.lang.String)
617     * @see Specification#getJavaTypeName()
618     * @see JavaTypeName#getName(boolean)
619     *
620     * @deprecated As of JOMC 1.4, please use method {@link Specification#getJavaTypeName()}. This method will be
621     * removed in JOMC 2.0.
622     */
623    @Deprecated
624    public String getJavaTypeName( final SpecificationReference reference, final boolean qualified )
625        throws ModelObjectException
626    {
627        if ( reference == null )
628        {
629            throw new NullPointerException( "reference" );
630        }
631
632        Specification s = null;
633        String typeName = null;
634
635        if ( this.getModules() != null
636                 && ( s = this.getModules().getSpecification( reference.getIdentifier() ) ) != null )
637        {
638            final JavaTypeName javaTypeName = s.getJavaTypeName();
639            typeName = javaTypeName != null ? javaTypeName.getName( qualified ) : null;
640        }
641        else if ( this.isLoggable( Level.WARNING ) )
642        {
643            this.log( Level.WARNING, getMessage( "specificationNotFound", reference.getIdentifier() ), null );
644        }
645
646        return typeName;
647    }
648
649    /**
650     * Gets the Java package name of an implementation.
651     *
652     * @param implementation The implementation to get the Java package name of.
653     *
654     * @return The Java package name of {@code implementation} or {@code null}, if the implementation does not reference
655     * a type.
656     *
657     * @throws NullPointerException if {@code implementation} is {@code null}.
658     * @throws ModelObjectException if compiling the name of the referenced type to a {@code JavaTypeName} fails.
659     *
660     * @see Implementation#getJavaTypeName()
661     * @see JavaTypeName#getPackageName()
662     *
663     * @deprecated As of JOMC 1.4, please use method {@link Implementation#getJavaTypeName()}. This method will be
664     * removed in JOMC 2.0.
665     */
666    @Deprecated
667    public String getJavaPackageName( final Implementation implementation ) throws ModelObjectException
668    {
669        if ( implementation == null )
670        {
671            throw new NullPointerException( "implementation" );
672        }
673
674        final JavaTypeName javaTypeName = implementation.getJavaTypeName();
675        return javaTypeName != null ? javaTypeName.getPackageName() : null;
676    }
677
678    /**
679     * Gets the Java type name of an implementation.
680     *
681     * @param implementation The implementation to get the Java type name of.
682     * @param qualified {@code true}, to return the fully qualified type name (with package name prepended);
683     * {@code false}, to return the short type name (without package name prepended).
684     *
685     * @return The Java type name of the type referenced by the implementation or {@code null}, if the implementation
686     * does not reference a type.
687     *
688     * @throws NullPointerException if {@code implementation} is {@code null}.
689     * @throws ModelObjectException if compiling the name of the referenced type to a {@code JavaTypeName} fails.
690     *
691     * @see Implementation#getJavaTypeName()
692     * @see JavaTypeName#getName(boolean)
693     *
694     * @deprecated As of JOMC 1.4, please use method {@link Implementation#getJavaTypeName()}. This method will be
695     * removed in JOMC 2.0.
696     */
697    @Deprecated
698    public String getJavaTypeName( final Implementation implementation, final boolean qualified )
699        throws ModelObjectException
700    {
701        if ( implementation == null )
702        {
703            throw new NullPointerException( "implementation" );
704        }
705
706        final JavaTypeName javaTypeName = implementation.getJavaTypeName();
707        return javaTypeName != null ? javaTypeName.getName( qualified ) : null;
708    }
709
710    /**
711     * Gets the Java class path location of an implementation.
712     *
713     * @param implementation The implementation to return the Java class path location of.
714     *
715     * @return The Java class path location of {@code implementation} or {@code null}, if the implementation does not
716     * reference a type.
717     *
718     * @throws NullPointerException if {@code implementation} is {@code null}.
719     * @throws ModelObjectException if compiling the name of the referenced type to a {@code JavaTypeName} fails.
720     *
721     * @see Implementation#getJavaTypeName()
722     * @see JavaTypeName#getQualifiedName()
723     *
724     * @deprecated As of JOMC 1.4, please use method {@link Implementation#getJavaTypeName()}. This method will be
725     * removed in JOMC 2.0.
726     */
727    @Deprecated
728    public String getJavaClasspathLocation( final Implementation implementation ) throws ModelObjectException
729    {
730        if ( implementation == null )
731        {
732            throw new NullPointerException( "implementation" );
733        }
734
735        final JavaTypeName javaTypeName = implementation.getJavaTypeName();
736        return javaTypeName != null ? javaTypeName.getQualifiedName().replace( '.', '/' ) : null;
737    }
738
739    /**
740     * Gets a list of names of all Java types an implementation implements.
741     *
742     * @param implementation The implementation to get names of all implemented Java types of.
743     * @param qualified {@code true}, to return the fully qualified type names (with package name prepended);
744     * {@code false}, to return the short type names (without package name prepended).
745     *
746     * @return An unmodifiable list of names of all Java types implemented by {@code implementation}.
747     *
748     * @throws NullPointerException if {@code implementation} is {@code null}.
749     * @throws ModelObjectException if compiling the name of a referenced type to a {@code JavaTypeName} fails.
750     *
751     * @deprecated As of JOMC 1.2, replaced by method {@link #getImplementedJavaTypeNames(org.jomc.model.Implementation, boolean)}.
752     * This method will be removed in version 2.0.
753     */
754    @Deprecated
755    public List<String> getJavaInterfaceNames( final Implementation implementation, final boolean qualified )
756        throws ModelObjectException
757    {
758        if ( implementation == null )
759        {
760            throw new NullPointerException( "implementation" );
761        }
762
763        return this.getImplementedJavaTypeNames( implementation, qualified );
764    }
765
766    /**
767     * Gets a list of names of all Java types an implementation implements.
768     *
769     * @param implementation The implementation to get names of all implemented Java types of.
770     * @param qualified {@code true}, to return the fully qualified type names (with package name prepended);
771     * {@code false}, to return the short type names (without package name prepended).
772     *
773     * @return An unmodifiable list of names of all Java types implemented by {@code implementation}.
774     *
775     * @throws NullPointerException if {@code implementation} is {@code null}.
776     * @throws ModelObjectException if compiling the name of the referenced type to a {@code JavaTypeName} fails.
777     *
778     * @since 1.2
779     *
780     * @deprecated As of JOMC 1.4, please use method {@link Modules#getImplementedJavaTypeNames(java.lang.String)}.
781     * This method will be removed in JOMC 2.0.
782     */
783    @Deprecated
784    public List<String> getImplementedJavaTypeNames( final Implementation implementation, final boolean qualified )
785        throws ModelObjectException
786    {
787        if ( implementation == null )
788        {
789            throw new NullPointerException( "implementation" );
790        }
791
792        List<String> col = null;
793
794        if ( this.getModules() != null )
795        {
796            final List<JavaTypeName> javaTypeNames =
797                this.getModules().getImplementedJavaTypeNames( implementation.getIdentifier() );
798
799            if ( javaTypeNames != null )
800            {
801                col = new ArrayList<String>( javaTypeNames.size() );
802
803                for ( int i = 0, s0 = javaTypeNames.size(); i < s0; i++ )
804                {
805                    if ( !col.contains( javaTypeNames.get( i ).getName( qualified ) ) )
806                    {
807                        col.add( javaTypeNames.get( i ).getName( qualified ) );
808                    }
809                }
810            }
811        }
812        else if ( this.isLoggable( Level.WARNING ) )
813        {
814            this.log( Level.WARNING, getMessage( "modulesNotFound", this.getModel().getIdentifier() ), null );
815        }
816
817        return Collections.unmodifiableList( col != null ? col : Collections.<String>emptyList() );
818    }
819
820    /**
821     * Gets the Java type name of an argument.
822     *
823     * @param argument The argument to get the Java type name of.
824     *
825     * @return The Java type name of the type referenced by the argument or {@code null}, if the argument does not
826     * reference a type.
827     *
828     * @throws NullPointerException if {@code argument} is {@code null}.
829     * @throws ModelObjectException if compiling the name of the referenced type to a {@code JavaTypeName} fails.
830     *
831     * @see Argument#getJavaTypeName()
832     * @see JavaTypeName#getName(boolean)
833     *
834     * @deprecated As of JOMC 1.4, please use method {@link Argument#getJavaTypeName()}. This method will be removed in
835     * JOMC 2.0.
836     */
837    @Deprecated
838    public String getJavaTypeName( final Argument argument ) throws ModelObjectException
839    {
840        if ( argument == null )
841        {
842            throw new NullPointerException( "argument" );
843        }
844
845        final JavaTypeName javaTypeName = argument.getJavaTypeName();
846        return javaTypeName != null ? javaTypeName.getName( true ) : null;
847    }
848
849    /**
850     * Gets a Java method parameter name of an argument.
851     *
852     * @param argument The argument to get the Java method parameter name of.
853     *
854     * @return The Java method parameter name of {@code argument}.
855     *
856     * @throws NullPointerException if {@code argument} is {@code null}.
857     * @throws ModelObjectException if compiling the name of the argument to a {@code JavaIdentifier} fails.
858     *
859     * @see Argument#getJavaVariableName()
860     *
861     * @since 1.2
862     *
863     * @deprecated As of JOMC 1.4, please use method {@link Argument#getJavaVariableName()}. This method will be
864     * removed in JOMC 2.0.
865     */
866    @Deprecated
867    public String getJavaMethodParameterName( final Argument argument ) throws ModelObjectException
868    {
869        if ( argument == null )
870        {
871            throw new NullPointerException( "argument" );
872        }
873
874        return this.getJavaMethodParameterName( argument.getName() );
875    }
876
877    /**
878     * Gets the Java type name of a property.
879     *
880     * @param property The property to get the Java type name of.
881     * @param boxify {@code true}, to return the name of the Java wrapper class when the type is a Java primitive type;
882     * {@code false}, to return the exact binary name (unboxed name) of the Java type.
883     *
884     * @return The Java type name of the type referenced by the property or {@code null}, if the property does not
885     * reference a type.
886     *
887     * @throws NullPointerException if {@code property} is {@code null}.
888     * @throws ModelObjectException if compiling the name of the referenced type to a {@code JavaTypeName} fails.
889     *
890     * @see Property#getJavaTypeName()
891     * @see JavaTypeName#getBoxedName()
892     * @see JavaTypeName#getName(boolean)
893     *
894     * @deprecated As of JOMC 1.4, please use method {@link Property#getJavaTypeName()}. This method will be removed in
895     * JOMC 2.0.
896     */
897    @Deprecated
898    public String getJavaTypeName( final Property property, final boolean boxify ) throws ModelObjectException
899    {
900        if ( property == null )
901        {
902            throw new NullPointerException( "property" );
903        }
904
905        JavaTypeName javaTypeName = property.getJavaTypeName();
906
907        if ( javaTypeName != null )
908        {
909            if ( boxify && javaTypeName.isPrimitive() )
910            {
911                javaTypeName = javaTypeName.getBoxedName();
912            }
913
914            return javaTypeName.getName( true );
915        }
916
917        return null;
918    }
919
920    /**
921     * Gets a flag indicating the type of a given property is a Java primitive.
922     *
923     * @param property The property to query.
924     *
925     * @return {@code true}, if the Java type referenced by the property is primitive or {@code false}, if the property
926     * does not reference a type or if the Java type referenced by the property is not primitive.
927     *
928     * @throws NullPointerException if {@code property} is {@code null}.
929     * @throws ModelObjectException if compiling the name of the referenced type to a {@code JavaTypeName} fails.
930     *
931     * @see Property#getJavaTypeName()
932     * @see JavaTypeName#isPrimitive()
933     *
934     * @deprecated As of JOMC 1.4, please use method {@link Property#getJavaTypeName()}. This method will be removed in
935     * JOMC 2.0.
936     */
937    @Deprecated
938    public boolean isJavaPrimitiveType( final Property property ) throws ModelObjectException
939    {
940        if ( property == null )
941        {
942            throw new NullPointerException( "property" );
943        }
944
945        final JavaTypeName javaTypeName = property.getJavaTypeName();
946        return javaTypeName != null && javaTypeName.isPrimitive();
947    }
948
949    /**
950     * Gets the name of a Java getter method of a given property.
951     *
952     * @param property The property to get a Java getter method name of.
953     *
954     * @return The Java getter method name of {@code property}.
955     *
956     * @throws NullPointerException if {@code property} is {@code null}.
957     * @throws ModelObjectException if compiling the name of the property to a {@code JavaIdentifier} fails.
958     *
959     * @see Property#getJavaGetterMethodName()
960     *
961     * @deprecated As of JOMC 1.4, please use method {@link Property#getJavaGetterMethodName()}. This method will be
962     * removed in JOMC 2.0.
963     */
964    @Deprecated
965    public String getJavaGetterMethodName( final Property property ) throws ModelObjectException
966    {
967        if ( property == null )
968        {
969            throw new NullPointerException( "property" );
970        }
971
972        String prefix = "get";
973
974        final String javaTypeName = this.getJavaTypeName( property, true );
975        if ( Boolean.class.getName().equals( javaTypeName ) )
976        {
977            prefix = "is";
978        }
979
980        return prefix + this.getJavaIdentifier( property.getName(), true );
981    }
982
983    /**
984     * Gets the name of a Java setter method of a given property.
985     *
986     * @param property The property to get a Java setter method name of.
987     *
988     * @return The Java setter method name of {@code property}.
989     *
990     * @throws NullPointerException if {@code property} is {@code null}.
991     * @throws ModelObjectException if compiling the name of the property to a {@code JavaIdentifier} fails.
992     *
993     * @see Property#getJavaSetterMethodName()
994     *
995     * @since 1.2
996     *
997     * @deprecated As of JOMC 1.4, please use method {@link Property#getJavaSetterMethodName()}. This method will be
998     * removed in JOMC 2.0.
999     */
1000    @Deprecated
1001    public String getJavaSetterMethodName( final Property property ) throws ModelObjectException
1002    {
1003        if ( property == null )
1004        {
1005            throw new NullPointerException( "property" );
1006        }
1007
1008        return "set" + this.getJavaIdentifier( property.getName(), true );
1009    }
1010
1011    /**
1012     * Gets a Java method parameter name of a property.
1013     *
1014     * @param property The property to get the Java method parameter name of.
1015     *
1016     * @return The Java method parameter name of {@code property}.
1017     *
1018     * @throws NullPointerException if {@code property} is {@code null}.
1019     * @throws ModelObjectException if copmiling the name of the property to a {@code JavaIdentifier} fails.
1020     *
1021     * @see Property#getJavaVariableName()
1022     *
1023     * @since 1.2
1024     *
1025     * @deprecated As of JOMC 1.4, please use method {@link Property#getJavaVariableName()}. This method will be
1026     * removed in JOMC 2.0.
1027     */
1028    @Deprecated
1029    public String getJavaMethodParameterName( final Property property ) throws ModelObjectException
1030    {
1031        if ( property == null )
1032        {
1033            throw new NullPointerException( "property" );
1034        }
1035
1036        return this.getJavaMethodParameterName( property.getName() );
1037    }
1038
1039    /**
1040     * Gets a Java field name of a property.
1041     *
1042     * @param property The property to get the Java field name of.
1043     *
1044     * @return The Java field name of {@code property}.
1045     *
1046     * @throws NullPointerException if {@code property} is {@code null}.
1047     * @throws ModelObjectException if compiling the name of the property to a {@code JavaIdentifier} fails.
1048     *
1049     * @see Property#getJavaVariableName()
1050     *
1051     * @since 1.3
1052     *
1053     * @deprecated As of JOMC 1.4, please use method {@link Property#getJavaVariableName()}. This method will be removed
1054     * in JOMC 2.0.
1055     */
1056    @Deprecated
1057    public String getJavaFieldName( final Property property ) throws ModelObjectException
1058    {
1059        if ( property == null )
1060        {
1061            throw new NullPointerException( "property" );
1062        }
1063
1064        return this.getJavaFieldName( property.getName() );
1065    }
1066
1067    /**
1068     * Gets the name of a Java type of a given dependency.
1069     *
1070     * @param dependency The dependency to get a dependency Java type name of.
1071     *
1072     * @return The Java type name of the dependency or {@code null}, if the referenced specification is not found or
1073     * does not reference a type.
1074     *
1075     * @throws NullPointerException if {@code dependency} is {@code null}.
1076     * @throws ModelObjectException if compiling the name of the referenced type to a {@code JavaTypeName} fails.
1077     *
1078     * @deprecated As of JOMC 1.4, please use method {@link Modules#getDependencyJavaTypeName(java.lang.String, java.lang.String)}.
1079     * This method will be removed in JOMC 2.0.
1080     */
1081    @Deprecated
1082    public String getJavaTypeName( final Dependency dependency ) throws ModelObjectException
1083    {
1084        if ( dependency == null )
1085        {
1086            throw new NullPointerException( "dependency" );
1087        }
1088
1089        Specification s = null;
1090        StringBuilder typeName = null;
1091        String javaTypeName = null;
1092
1093        try
1094        {
1095            if ( this.getModules() != null
1096                     && ( s = this.getModules().getSpecification( dependency.getIdentifier() ) ) != null )
1097            {
1098                if ( s.getClazz() != null )
1099                {
1100                    typeName = new StringBuilder( s.getClazz().length() );
1101                    typeName.append( this.getJavaTypeName( s, true ) );
1102
1103                    if ( s.getMultiplicity() == Multiplicity.MANY && dependency.getImplementationName() == null )
1104                    {
1105                        typeName.append( "[]" );
1106                    }
1107
1108                    javaTypeName = JavaTypeName.parse( typeName.toString() ).getName( true );
1109                }
1110            }
1111            else if ( this.isLoggable( Level.WARNING ) )
1112            {
1113                this.log( Level.WARNING, getMessage( "specificationNotFound", dependency.getIdentifier() ), null );
1114            }
1115
1116            return javaTypeName;
1117        }
1118        catch ( final ParseException e )
1119        {
1120            throw new ModelObjectException( getMessage( "dependencyJavaTypeNameParseException", typeName,
1121                                                        getMessage( e ) ), e );
1122
1123        }
1124    }
1125
1126    /**
1127     * Gets the name of a Java getter method of a given dependency.
1128     *
1129     * @param dependency The dependency to get a Java getter method name of.
1130     *
1131     * @return The Java getter method name of {@code dependency}.
1132     *
1133     * @throws NullPointerException if {@code dependency} is {@code null}.
1134     * @throws ModelObjectException if compiling the name of the dependency to a {@code JavaIdentifier} fails.
1135     *
1136     * @see Dependency#getJavaGetterMethodName()
1137     *
1138     * @deprecated As of JOMC 1.4, please use method {@link Dependency#getJavaGetterMethodName()}. This method will be
1139     * removed in JOMC 2.0.
1140     */
1141    @Deprecated
1142    public String getJavaGetterMethodName( final Dependency dependency ) throws ModelObjectException
1143    {
1144        if ( dependency == null )
1145        {
1146            throw new NullPointerException( "dependency" );
1147        }
1148
1149        return "get" + this.getJavaIdentifier( dependency.getName(), true );
1150    }
1151
1152    /**
1153     * Gets the name of a Java setter method of a given dependency.
1154     *
1155     * @param dependency The dependency to get a Java setter method name of.
1156     *
1157     * @return The Java setter method name of {@code dependency}.
1158     *
1159     * @throws NullPointerException if {@code dependency} is {@code null}.
1160     * @throws ModelObjectException if compiling the name of the dependency to a {@code JavaIdentifier} fails.
1161     *
1162     * @see Dependency#getJavaSetterMethodName()
1163     *
1164     * @since 1.2
1165     *
1166     * @deprecated As of JOMC 1.4, please use method {@link Dependency#getJavaSetterMethodName()}. This method will be
1167     * removed in JOMC 2.0.
1168     */
1169    @Deprecated
1170    public String getJavaSetterMethodName( final Dependency dependency ) throws ModelObjectException
1171    {
1172        if ( dependency == null )
1173        {
1174            throw new NullPointerException( "dependency" );
1175        }
1176
1177        return "set" + this.getJavaIdentifier( dependency.getName(), true );
1178    }
1179
1180    /**
1181     * Gets a Java method parameter name of a dependency.
1182     *
1183     * @param dependency The dependency to get the Java method parameter name of.
1184     *
1185     * @return The Java method parameter name of {@code dependency}.
1186     *
1187     * @throws NullPointerException if {@code dependency} is {@code null}.
1188     * @throws ModelObjectException if compiling the name of the dependency to a {@code JavaIdentifier} fails.
1189     *
1190     * @see Dependency#getJavaVariableName()
1191     *
1192     * @since 1.2
1193     *
1194     * @deprecated As of JOMC 1.4, please use method {@link Dependency#getJavaVariableName()}. This method will be
1195     * removed in JOMC 2.0.
1196     */
1197    @Deprecated
1198    public String getJavaMethodParameterName( final Dependency dependency ) throws ModelObjectException
1199    {
1200        if ( dependency == null )
1201        {
1202            throw new NullPointerException( "dependency" );
1203        }
1204
1205        return this.getJavaMethodParameterName( dependency.getName() );
1206    }
1207
1208    /**
1209     * Gets a Java field name of a dependency.
1210     *
1211     * @param dependency The dependency to get the Java field name of.
1212     *
1213     * @return The Java field name of {@code dependency}.
1214     *
1215     * @throws NullPointerException if {@code dependency} is {@code null}.
1216     * @throws ModelObjectException if compiling the name of the dependency to a {@code JavaIdentifier} fails.
1217     *
1218     * @see Dependency#getJavaVariableName()
1219     *
1220     * @since 1.3
1221     *
1222     * @deprecated As of JOMC 1.4, please use method {@link Dependency#getJavaVariableName()}. This method will be
1223     * removed in JOMC 2.0.
1224     */
1225    @Deprecated
1226    public String getJavaFieldName( final Dependency dependency ) throws ModelObjectException
1227    {
1228        if ( dependency == null )
1229        {
1230            throw new NullPointerException( "dependency" );
1231        }
1232
1233        return this.getJavaFieldName( dependency.getName() );
1234    }
1235
1236    /**
1237     * Gets the name of a Java getter method of a given message.
1238     *
1239     * @param message The message to get a Java getter method name of.
1240     *
1241     * @return The Java getter method name of {@code message}.
1242     *
1243     * @throws NullPointerException if {@code message} is {@code null}.
1244     * @throws ModelObjectException if compiling the name of the message to a {@code JavaIdentifier} fails.
1245     *
1246     * @see Message#getJavaGetterMethodName()
1247     *
1248     * @deprecated As of JOMC 1.4, please use method {@link Message#getJavaGetterMethodName()}. This method will be
1249     * removed in JOMC 2.0.
1250     */
1251    @Deprecated
1252    public String getJavaGetterMethodName( final Message message ) throws ModelObjectException
1253    {
1254        if ( message == null )
1255        {
1256            throw new NullPointerException( "message" );
1257        }
1258
1259        return "get" + this.getJavaIdentifier( message.getName(), true );
1260    }
1261
1262    /**
1263     * Gets the name of a Java setter method of a given message.
1264     *
1265     * @param message The message to get a Java setter method name of.
1266     *
1267     * @return The Java setter method name of {@code message}.
1268     *
1269     * @throws NullPointerException if {@code message} is {@code null}.
1270     * @throws ModelObjectException if compiling the name of the message to a {@code JavaIdentifier} fails.
1271     *
1272     * @see Message#getJavaSetterMethodName()
1273     *
1274     * @since 1.2
1275     *
1276     * @deprecated As of JOMC 1.4, please use method {@link Message#getJavaSetterMethodName()}. This method will be
1277     * removed in JOMC 2.0.
1278     */
1279    @Deprecated
1280    public String getJavaSetterMethodName( final Message message ) throws ModelObjectException
1281    {
1282        if ( message == null )
1283        {
1284            throw new NullPointerException( "message" );
1285        }
1286
1287        return "set" + this.getJavaIdentifier( message.getName(), true );
1288    }
1289
1290    /**
1291     * Gets a Java method parameter name of a message.
1292     *
1293     * @param message The message to get the Java method parameter name of.
1294     *
1295     * @return The Java method parameter name of {@code message}.
1296     *
1297     * @throws NullPointerException if {@code message} is {@code null}.
1298     * @throws ModelObjectException if compiling the name of the message to a {@code JavaIdentifier} fails.
1299     *
1300     * @see Message#getJavaVariableName()
1301     *
1302     * @since 1.2
1303     *
1304     * @deprecated As of JOMC 1.4, please use method {@link Message#getJavaVariableName()}. This method will be removed
1305     * in JOMC 2.0.
1306     */
1307    @Deprecated
1308    public String getJavaMethodParameterName( final Message message ) throws ModelObjectException
1309    {
1310        if ( message == null )
1311        {
1312            throw new NullPointerException( "message" );
1313        }
1314
1315        return this.getJavaMethodParameterName( message.getName() );
1316    }
1317
1318    /**
1319     * Gets a Java field name of a message.
1320     *
1321     * @param message The message to get the Java field name of.
1322     *
1323     * @return The Java field name of {@code message}.
1324     *
1325     * @throws NullPointerException if {@code message} is {@code null}.
1326     * @throws ModelObjectException if compiling the name of the message to a {@code JavaIdentifier} fails.
1327     *
1328     * @see Message#getJavaVariableName()
1329     *
1330     * @since 1.3
1331     *
1332     * @deprecated As of JOMC 1.4, please use method {@link Message#getJavaVariableName()}. This method will be removed
1333     * in JOMC 2.0.
1334     */
1335    @Deprecated
1336    public String getJavaFieldName( final Message message ) throws ModelObjectException
1337    {
1338        if ( message == null )
1339        {
1340            throw new NullPointerException( "message" );
1341        }
1342
1343        return this.getJavaFieldName( message.getName() );
1344    }
1345
1346    /**
1347     * Gets the Java modifier name of a dependency of a given implementation.
1348     *
1349     * @param implementation The implementation declaring the dependency to get a Java modifier name of.
1350     * @param dependency The dependency to get a Java modifier name of.
1351     *
1352     * @return The Java modifier name of {@code dependency} of {@code implementation}.
1353     *
1354     * @throws NullPointerException if {@code implementation} or {@code dependency} is {@code null}.
1355     *
1356     * @deprecated As of JOMC 1.4, please use method {@link Modules#getDependencyJavaModifierName(java.lang.String, java.lang.String)}.
1357     * This method will be removed in JOMC 2.0.
1358     */
1359    @Deprecated
1360    public String getJavaModifierName( final Implementation implementation, final Dependency dependency )
1361    {
1362        if ( implementation == null )
1363        {
1364            throw new NullPointerException( "implementation" );
1365        }
1366        if ( dependency == null )
1367        {
1368            throw new NullPointerException( "dependency" );
1369        }
1370
1371        String modifierName = "private";
1372
1373        if ( this.getModules() != null )
1374        {
1375            modifierName =
1376                this.getModules().getDependencyJavaModifierName( implementation.getIdentifier(), dependency.getName() );
1377
1378            if ( modifierName == null )
1379            {
1380                modifierName = "private";
1381            }
1382        }
1383
1384        return modifierName;
1385    }
1386
1387    /**
1388     * Gets the Java modifier name of a message of a given implementation.
1389     *
1390     * @param implementation The implementation declaring the message to get a Java modifier name of.
1391     * @param message The message to get a Java modifier name of.
1392     *
1393     * @return The Java modifier name of {@code message} of {@code implementation}.
1394     *
1395     * @throws NullPointerException if {@code implementation} or {@code message} is {@code null}.
1396     *
1397     * @deprecated As of JOMC 1.4, please use method {@link Modules#getMessageJavaModifierName(java.lang.String, java.lang.String)}.
1398     * This method will be removed in JOMC 2.0.
1399     */
1400    @Deprecated
1401    public String getJavaModifierName( final Implementation implementation, final Message message )
1402    {
1403        if ( implementation == null )
1404        {
1405            throw new NullPointerException( "implementation" );
1406        }
1407        if ( message == null )
1408        {
1409            throw new NullPointerException( "message" );
1410        }
1411
1412        String modifierName = "private";
1413
1414        if ( this.getModules() != null )
1415        {
1416            modifierName =
1417                this.getModules().getMessageJavaModifierName( implementation.getIdentifier(), message.getName() );
1418
1419            if ( modifierName == null )
1420            {
1421                modifierName = "private";
1422            }
1423        }
1424
1425        return modifierName;
1426    }
1427
1428    /**
1429     * Gets the Java modifier name of a property of a given implementation.
1430     *
1431     * @param implementation The implementation declaring the property to get a Java modifier name of.
1432     * @param property The property to get a Java modifier name of.
1433     *
1434     * @return The Java modifier name of {@code property} of {@code implementation}.
1435     *
1436     * @throws NullPointerException if {@code implementation} or {@code property} is {@code null}.
1437     *
1438     * @deprecated As of JOMC 1.4, please use method {@link Modules#getPropertyJavaModifierName(java.lang.String, java.lang.String)}.
1439     * This method will be removed in JOMC 2.0.
1440     */
1441    @Deprecated
1442    public String getJavaModifierName( final Implementation implementation, final Property property )
1443    {
1444        if ( implementation == null )
1445        {
1446            throw new NullPointerException( "implementation" );
1447        }
1448        if ( property == null )
1449        {
1450            throw new NullPointerException( "property" );
1451        }
1452
1453        String modifierName = "private";
1454
1455        if ( this.getModules() != null )
1456        {
1457            modifierName =
1458                this.getModules().getPropertyJavaModifierName( implementation.getIdentifier(), property.getName() );
1459
1460            if ( modifierName == null )
1461            {
1462                modifierName = "private";
1463            }
1464        }
1465
1466        return modifierName;
1467    }
1468
1469    /**
1470     * Formats a text to a Javadoc comment.
1471     *
1472     * @param text The text to format to a Javadoc comment.
1473     * @param indentationLevel The indentation level of the comment.
1474     * @param linePrefix The text to prepend lines with.
1475     *
1476     * @return {@code text} formatted to a Javadoc comment.
1477     *
1478     * @throws NullPointerException if {@code text} or {@code linePrefix} is {@code null}.
1479     * @throws IllegalArgumentException if {@code indentationLevel} is negative.
1480     * @throws ModelObjectException if compiling the type of the text to a {@code MimeType} fails.
1481     *
1482     * @deprecated As of JOMC 1.4, please use method {@link Text#getJavadocComment(java.lang.String, java.lang.String)}.
1483     * This method will be removed in JOMC 2.0.
1484     */
1485    @Deprecated
1486    public String getJavadocComment( final Text text, final int indentationLevel, final String linePrefix )
1487        throws ModelObjectException
1488    {
1489        if ( text == null )
1490        {
1491            throw new NullPointerException( "text" );
1492        }
1493        if ( linePrefix == null )
1494        {
1495            throw new NullPointerException( "linePrefix" );
1496        }
1497        if ( indentationLevel < 0 )
1498        {
1499            throw new IllegalArgumentException( Integer.toString( indentationLevel ) );
1500        }
1501
1502        BufferedReader reader = null;
1503        boolean suppressExceptionOnClose = true;
1504
1505        try
1506        {
1507            String javadoc = "";
1508
1509            if ( text.getValue() != null )
1510            {
1511                final String indent = this.getIndentation( indentationLevel );
1512                reader = new BufferedReader( new StringReader( text.getValue() ) );
1513                final StringBuilder builder = new StringBuilder( text.getValue().length() );
1514
1515                String line;
1516                while ( ( line = reader.readLine() ) != null )
1517                {
1518                    builder.append( this.getLineSeparator() ).append( indent ).append( linePrefix ).
1519                        append( line.replaceAll( "\\/\\*\\*", "/*" ).replaceAll( "\\*/", "/" ) );
1520
1521                }
1522
1523                if ( builder.length() > 0 )
1524                {
1525                    javadoc =
1526                        builder.substring( this.getLineSeparator().length() + indent.length() + linePrefix.length() );
1527
1528                    if ( !text.getMimeType().match( "text/html" ) )
1529                    {
1530                        javadoc = StringEscapeUtils.escapeHtml( javadoc );
1531                    }
1532                }
1533            }
1534
1535            suppressExceptionOnClose = false;
1536            return javadoc;
1537        }
1538        catch ( final MimeTypeParseException e )
1539        {
1540            throw new AssertionError( e );
1541        }
1542        catch ( final IOException e )
1543        {
1544            throw new AssertionError( e );
1545        }
1546        finally
1547        {
1548            try
1549            {
1550                if ( reader != null )
1551                {
1552                    reader.close();
1553                }
1554            }
1555            catch ( final IOException e )
1556            {
1557                if ( suppressExceptionOnClose )
1558                {
1559                    this.log( Level.SEVERE, getMessage( e ), e );
1560                }
1561                else
1562                {
1563                    throw new AssertionError( e );
1564                }
1565            }
1566        }
1567    }
1568
1569    /**
1570     * Formats a text from a list of texts to a Javadoc comment.
1571     *
1572     * @param texts The list of texts to format to a Javadoc comment.
1573     * @param indentationLevel The indentation level of the comment.
1574     * @param linePrefix The text to prepend lines with.
1575     *
1576     * @return The text corresponding to the locale of the instance from the list of texts formatted to a Javadoc
1577     * comment.
1578     *
1579     * @throws NullPointerException if {@code texts} or {@code linePrefix} is {@code null}.
1580     * @throws IllegalArgumentException if {@code indentationLevel} is negative.
1581     * @throws ModelObjectException if compiling a referenced type to a {@code MimeType} fails.
1582     *
1583     * @see #getLocale()
1584     *
1585     * @since 1.2
1586     *
1587     * @deprecated As of JOMC 1.4, please use method {@link Text#getJavadocComment(java.lang.String, java.lang.String)}.
1588     * This method will be removed in JOMC 2.0.
1589     */
1590    @Deprecated
1591    public String getJavadocComment( final Texts texts, final int indentationLevel, final String linePrefix )
1592        throws ModelObjectException
1593    {
1594        if ( texts == null )
1595        {
1596            throw new NullPointerException( "texts" );
1597        }
1598        if ( linePrefix == null )
1599        {
1600            throw new NullPointerException( "linePrefix" );
1601        }
1602        if ( indentationLevel < 0 )
1603        {
1604            throw new IllegalArgumentException( Integer.toString( indentationLevel ) );
1605        }
1606
1607        return this.getJavadocComment( texts.getText( this.getLocale().getLanguage() ), indentationLevel, linePrefix );
1608    }
1609
1610    /**
1611     * Formats a string to a Java string with unicode escapes.
1612     *
1613     * @param str The string to format to a Java string or {@code null}.
1614     *
1615     * @return {@code str} formatted to a Java string or {@code null}.
1616     *
1617     * @see StringEscapeUtils#escapeJava(java.lang.String)
1618     */
1619    public String getJavaString( final String str )
1620    {
1621        return StringEscapeUtils.escapeJava( str );
1622    }
1623
1624    /**
1625     * Formats a string to a Java class path location.
1626     *
1627     * @param str The string to format or {@code null}.
1628     * @param absolute {@code true} to return an absolute class path location; {@code false} to return a relative
1629     * class path location.
1630     *
1631     * @return {@code str} formatted to a Java class path location.
1632     *
1633     * @since 1.3
1634     *
1635     * @deprecated As of JOMC 1.4, please use {@link JavaTypeName#getQualifiedName()}. This method will be removed in
1636     * JOMC 2.0.
1637     */
1638    @Deprecated
1639    public String getJavaClasspathLocation( final String str, final boolean absolute )
1640    {
1641        String classpathLocation = null;
1642
1643        if ( str != null )
1644        {
1645            classpathLocation = str.replace( '.', '/' );
1646
1647            if ( absolute )
1648            {
1649                classpathLocation = "/" + classpathLocation;
1650            }
1651        }
1652
1653        return classpathLocation;
1654    }
1655
1656    /**
1657     * Formats a string to a Java identifier.
1658     *
1659     * @param str The string to format or {@code null}.
1660     * @param capitalize {@code true}, to return an identifier with the first character upper cased; {@code false}, to
1661     * return an identifier with the first character lower cased.
1662     *
1663     * @return {@code str} formatted to a Java identifier or {@code null}.
1664     *
1665     * @since 1.2
1666     *
1667     * @deprecated As of JOMC 1.4, please use method {@link #toJavaVariableName(java.lang.String)}. This method will be
1668     * removed in JOMC 2.0.
1669     */
1670    @Deprecated
1671    public String getJavaIdentifier( final String str, final boolean capitalize )
1672    {
1673        String identifier = null;
1674
1675        if ( str != null )
1676        {
1677            final int len = str.length();
1678            final StringBuilder builder = new StringBuilder( len );
1679            boolean uc = capitalize;
1680
1681            for ( int i = 0; i < len; i++ )
1682            {
1683                final char c = str.charAt( i );
1684                final String charString = Character.toString( c );
1685
1686                if ( builder.length() > 0 )
1687                {
1688                    if ( Character.isJavaIdentifierPart( c ) )
1689                    {
1690                        builder.append( uc ? charString.toUpperCase( this.getLocale() ) : charString );
1691                        uc = false;
1692                    }
1693                    else
1694                    {
1695                        uc = true;
1696                    }
1697                }
1698                else if ( Character.isJavaIdentifierStart( c ) )
1699                {
1700                    builder.append( uc
1701                                        ? charString.toUpperCase( this.getLocale() )
1702                                        : charString.toLowerCase( this.getLocale() ) );
1703
1704                    uc = false;
1705                }
1706                else
1707                {
1708                    uc = capitalize;
1709                }
1710            }
1711
1712            identifier = builder.toString();
1713
1714            if ( identifier.length() <= 0 && this.isLoggable( Level.WARNING ) )
1715            {
1716                this.log( Level.WARNING, getMessage( "invalidJavaIdentifier", str ), null );
1717            }
1718        }
1719
1720        return identifier;
1721    }
1722
1723    /**
1724     * Formats a string to a Java method parameter name.
1725     *
1726     * @param str The string to format or {@code null}.
1727     *
1728     * @return {@code str} formatted to a Java method parameter name or {@code null}.
1729     *
1730     * @since 1.3
1731     *
1732     * @deprecated As of JOMC 1.4, please use method {@link #toJavaVariableName(java.lang.String)}. This method will be
1733     * removed in JOMC 2.0.
1734     */
1735    @Deprecated
1736    public String getJavaMethodParameterName( final String str )
1737    {
1738        String methodParameterName = null;
1739
1740        if ( str != null )
1741        {
1742            final int len = str.length();
1743            final StringBuilder builder = new StringBuilder( len );
1744            boolean uc = false;
1745
1746            for ( int i = 0; i < len; i++ )
1747            {
1748                final char c = str.charAt( i );
1749                final String charString = Character.toString( c );
1750
1751                if ( builder.length() > 0 )
1752                {
1753                    if ( Character.isJavaIdentifierPart( c ) )
1754                    {
1755                        builder.append( uc ? charString.toUpperCase( this.getLocale() ) : charString );
1756                        uc = false;
1757                    }
1758                    else
1759                    {
1760                        uc = true;
1761                    }
1762                }
1763                else if ( Character.isJavaIdentifierStart( c ) )
1764                {
1765                    builder.append( charString.toLowerCase( this.getLocale() ) );
1766                }
1767            }
1768
1769            methodParameterName = builder.toString();
1770
1771            if ( methodParameterName.length() <= 0 && this.isLoggable( Level.WARNING ) )
1772            {
1773                this.log( Level.WARNING, getMessage( "invalidJavaMethodParameterName", str ), null );
1774            }
1775
1776            if ( this.getJavaKeywords().contains( methodParameterName ) )
1777            {
1778                methodParameterName = "_" + methodParameterName;
1779            }
1780        }
1781
1782        return methodParameterName;
1783    }
1784
1785    /**
1786     * Formats a string to a Java field name.
1787     *
1788     * @param str The string to format or {@code null}.
1789     *
1790     * @return {@code str} formatted to a Java field name or {@code null}.
1791     *
1792     * @since 1.3
1793     *
1794     * @deprecated As of JOMC 1.4, please use method {@link #toJavaVariableName(java.lang.String)}. This method will be
1795     * removed in JOMC 2.0.
1796     */
1797    @Deprecated
1798    public String getJavaFieldName( final String str )
1799    {
1800        String fieldName = null;
1801
1802        if ( str != null )
1803        {
1804            final int len = str.length();
1805            final StringBuilder builder = new StringBuilder( len );
1806            boolean uc = false;
1807
1808            for ( int i = 0; i < len; i++ )
1809            {
1810                final char c = str.charAt( i );
1811                final String charString = Character.toString( c );
1812
1813                if ( builder.length() > 0 )
1814                {
1815                    if ( Character.isJavaIdentifierPart( c ) )
1816                    {
1817                        builder.append( uc ? charString.toUpperCase( this.getLocale() ) : charString );
1818                        uc = false;
1819                    }
1820                    else
1821                    {
1822                        uc = true;
1823                    }
1824                }
1825                else if ( Character.isJavaIdentifierStart( c ) )
1826                {
1827                    builder.append( charString.toLowerCase( this.getLocale() ) );
1828                }
1829            }
1830
1831            fieldName = builder.toString();
1832
1833            if ( fieldName.length() <= 0 && this.isLoggable( Level.WARNING ) )
1834            {
1835                this.log( Level.WARNING, getMessage( "invalidJavaFieldName", str ), null );
1836            }
1837
1838            if ( this.getJavaKeywords().contains( fieldName ) )
1839            {
1840                fieldName = "_" + fieldName;
1841            }
1842        }
1843
1844        return fieldName;
1845    }
1846
1847    /**
1848     * Formats a string to a Java constant name.
1849     *
1850     * @param str The string to format or {@code null}.
1851     *
1852     * @return {@code str} formatted to a Java constant name or {@code null}.
1853     *
1854     * @since 1.3
1855     *
1856     * @deprecated As of JOMC 1.4, please use method {@link #toJavaConstantName(java.lang.String)}. This method will be
1857     * removed in JOMC 2.0.
1858     */
1859    @Deprecated
1860    public String getJavaConstantName( final String str )
1861    {
1862        String name = null;
1863
1864        if ( str != null )
1865        {
1866            final int len = str.length();
1867            final StringBuilder builder = new StringBuilder( len );
1868            boolean separator = false;
1869
1870            for ( int i = 0; i < len; i++ )
1871            {
1872                final char c = str.charAt( i );
1873
1874                if ( builder.length() > 0 ? Character.isJavaIdentifierPart( c ) : Character.isJavaIdentifierStart( c ) )
1875                {
1876                    if ( builder.length() > 0 )
1877                    {
1878                        if ( !separator )
1879                        {
1880                            final char previous = builder.charAt( builder.length() - 1 );
1881                            separator = Character.isLowerCase( previous ) && Character.isUpperCase( c );
1882                        }
1883
1884                        if ( separator )
1885                        {
1886                            builder.append( '_' );
1887                        }
1888                    }
1889
1890                    builder.append( c );
1891                    separator = false;
1892                }
1893                else
1894                {
1895                    separator = true;
1896                }
1897            }
1898
1899            name = builder.toString().toUpperCase( this.getLocale() );
1900
1901            if ( name.length() <= 0 && this.isLoggable( Level.WARNING ) )
1902            {
1903                this.log( Level.WARNING, getMessage( "invalidJavaConstantName", str ), null );
1904            }
1905        }
1906
1907        return name;
1908    }
1909
1910    /**
1911     * Compiles a string to a Java constant name.
1912     *
1913     * @param str The string to compile or {@code null}.
1914     *
1915     * @return {@code str} compiled to a {@code JavaIdentifier} or {@code null}, if {@code str} is {@code null}.
1916     *
1917     * @throws ParseException if compiling {@code str} to a {@code JavaIdentifier} fails.
1918     *
1919     * @since 1.3
1920     *
1921     * @see JavaIdentifier#normalize(java.lang.String, org.jomc.model.JavaIdentifier.NormalizationMode)
1922     * @see org.jomc.model.JavaIdentifier.NormalizationMode#CONSTANT_NAME_CONVENTION
1923     */
1924    public JavaIdentifier toJavaConstantName( final String str ) throws ParseException
1925    {
1926        JavaIdentifier constantName = null;
1927
1928        if ( str != null )
1929        {
1930            constantName = JavaIdentifier.normalize( str, JavaIdentifier.NormalizationMode.CONSTANT_NAME_CONVENTION );
1931        }
1932
1933        return constantName;
1934    }
1935
1936    /**
1937     * Compiles a string to a Java method name.
1938     *
1939     * @param str The string to compile or {@code null}.
1940     *
1941     * @return {@code str} compiled to a {@code JavaIdentifier} or {@code null}, if {@code str} is {@code null}.
1942     *
1943     * @throws ParseException if compiling {@code str} to a {@code JavaIdentifier} fails.
1944     *
1945     * @since 1.4
1946     *
1947     * @see JavaIdentifier#normalize(java.lang.String, org.jomc.model.JavaIdentifier.NormalizationMode)
1948     * @see org.jomc.model.JavaIdentifier.NormalizationMode#METHOD_NAME_CONVENTION
1949     */
1950    public JavaIdentifier toJavaMethodName( final String str ) throws ParseException
1951    {
1952        JavaIdentifier variableName = null;
1953
1954        if ( str != null )
1955        {
1956            variableName =
1957                JavaIdentifier.normalize( str, JavaIdentifier.NormalizationMode.METHOD_NAME_CONVENTION );
1958
1959        }
1960
1961        return variableName;
1962    }
1963
1964    /**
1965     * Compiles a string to a Java variable name.
1966     *
1967     * @param str The string to compile or {@code null}.
1968     *
1969     * @return {@code str} compiled to a {@code JavaIdentifier} or {@code null}, if {@code str} is {@code null}.
1970     *
1971     * @throws ParseException if compiling {@code str} to a {@code JavaIdentifier} fails.
1972     *
1973     * @since 1.4
1974     *
1975     * @see JavaIdentifier#normalize(java.lang.String, org.jomc.model.JavaIdentifier.NormalizationMode)
1976     * @see org.jomc.model.JavaIdentifier.NormalizationMode#VARIABLE_NAME_CONVENTION
1977     */
1978    public JavaIdentifier toJavaVariableName( final String str ) throws ParseException
1979    {
1980        JavaIdentifier variableName = null;
1981
1982        if ( str != null )
1983        {
1984            variableName =
1985                JavaIdentifier.normalize( str, JavaIdentifier.NormalizationMode.VARIABLE_NAME_CONVENTION );
1986
1987        }
1988
1989        return variableName;
1990    }
1991
1992    /**
1993     * Gets a flag indicating the type referenced by a given specification is located in an unnamed Java package.
1994     *
1995     * @param specification The specification to query.
1996     *
1997     * @return {@code true}, if the type referenced by {@code specification} is located in an unnamed Java package;
1998     * {@code false}, if the specification does not reference a type or if the referenced type is not located in an
1999     * unnamed Java package.
2000     *
2001     * @throws NullPointerException if {@code specification} is {@code null}.
2002     * @throws ModelObjectException if compiling the name of the referenced type to a {@code JavaTypeName} fails.
2003     *
2004     * @see Specification#getJavaTypeName()
2005     * @see JavaTypeName#isUnnamedPackage()
2006     *
2007     * @deprecated As of JOMC 1.4, please use method {@link Specification#getJavaTypeName()}. This method will be
2008     * removed in JOMC 2.0.
2009     */
2010    @Deprecated
2011    public boolean isJavaDefaultPackage( final Specification specification ) throws ModelObjectException
2012    {
2013        if ( specification == null )
2014        {
2015            throw new NullPointerException( "specification" );
2016        }
2017
2018        final JavaTypeName javaTypeName = specification.getJavaTypeName();
2019        return javaTypeName != null && javaTypeName.isUnnamedPackage();
2020    }
2021
2022    /**
2023     * Gets a flag indicating the type referenced by a given implementation is located in an unnamed Java package.
2024     *
2025     * @param implementation The implementation to query.
2026     *
2027     * @return {@code true}, if the type referenced by {@code implementation} is located in an unnamed Java package;
2028     * {@code false}, if the implementation does not reference a type or if the referenced type is not located in an
2029     * unnamed Java package.
2030     *
2031     * @throws NullPointerException if {@code implementation} is {@code null}.
2032     * @throws ModelObjectException if compiling the name of the referenced type to a {@code JavaTypeName} fails.
2033     *
2034     * @see Implementation#getJavaTypeName()
2035     * @see JavaTypeName#isUnnamedPackage()
2036     *
2037     * @deprecated As of JOMC 1.4, please use method {@link Implementation#getJavaTypeName()}. This method will be
2038     * removed in JOMC 2.0.
2039     */
2040    @Deprecated
2041    public boolean isJavaDefaultPackage( final Implementation implementation ) throws ModelObjectException
2042    {
2043        if ( implementation == null )
2044        {
2045            throw new NullPointerException( "implementation" );
2046        }
2047
2048        final JavaTypeName javaTypeName = implementation.getJavaTypeName();
2049        return javaTypeName != null && javaTypeName.isUnnamedPackage();
2050    }
2051
2052    /**
2053     * Formats a string to a HTML string with HTML entities.
2054     *
2055     * @param str The string to format to a HTML string with HTML entities or {@code null}.
2056     *
2057     * @return {@code str} formatted to a HTML string with HTML entities or {@code null}.
2058     *
2059     * @since 1.2
2060     */
2061    public String getHtmlString( final String str )
2062    {
2063        return str != null ? str.replace( "&", "&amp;" ).replace( "<", "&lt;" ).replace( ">", "&gt;" ).
2064            replace( "\"", "&quot;" ).replace( "*", "&lowast;" ) : null;
2065
2066    }
2067
2068    /**
2069     * Formats a string to a XML string with XML entities.
2070     *
2071     * @param str The string to format to a XML string with XML entities or {@code null}.
2072     *
2073     * @return {@code str} formatted to a XML string with XML entities or {@code null}.
2074     *
2075     * @see StringEscapeUtils#escapeXml(java.lang.String)
2076     *
2077     * @since 1.2
2078     */
2079    public String getXmlString( final String str )
2080    {
2081        return StringEscapeUtils.escapeXml( str );
2082    }
2083
2084    /**
2085     * Formats a string to a JavaScript string applying JavaScript string rules.
2086     *
2087     * @param str The string to format to a JavaScript string by applying JavaScript string rules or {@code null}.
2088     *
2089     * @return {@code str} formatted to a JavaScript string with JavaScript string rules applied or {@code null}.
2090     *
2091     * @see StringEscapeUtils#escapeJavaScript(java.lang.String)
2092     *
2093     * @since 1.2
2094     */
2095    public String getJavaScriptString( final String str )
2096    {
2097        return StringEscapeUtils.escapeJavaScript( str );
2098    }
2099
2100    /**
2101     * Formats a string to a SQL string.
2102     *
2103     * @param str The string to format to a SQL string or {@code null}.
2104     *
2105     * @return {@code str} formatted to a SQL string or {@code null}.
2106     *
2107     * @see StringEscapeUtils#escapeSql(java.lang.String)
2108     *
2109     * @since 1.2
2110     */
2111    public String getSqlString( final String str )
2112    {
2113        return StringEscapeUtils.escapeSql( str );
2114    }
2115
2116    /**
2117     * Formats a string to a CSV string.
2118     *
2119     * @param str The string to format to a CSV string or {@code null}.
2120     *
2121     * @return {@code str} formatted to a CSV string or {@code null}.
2122     *
2123     * @see StringEscapeUtils#escapeCsv(java.lang.String)
2124     *
2125     * @since 1.2
2126     */
2127    public String getCsvString( final String str )
2128    {
2129        return StringEscapeUtils.escapeCsv( str );
2130    }
2131
2132    /**
2133     * Formats a {@code Boolean} to a string.
2134     *
2135     * @param b The {@code Boolean} to format to a string or {@code null}.
2136     *
2137     * @return {@code b} formatted to a string.
2138     *
2139     * @see #getLocale()
2140     *
2141     * @since 1.2
2142     */
2143    public String getBooleanString( final Boolean b )
2144    {
2145        return ResourceBundle.getBundle( JomcTool.class.getName(), this.getLocale() ).
2146            getString( b ? "booleanStringTrue" : "booleanStringFalse" );
2147
2148    }
2149
2150    /**
2151     * Gets the display language of a given language code.
2152     *
2153     * @param language The language code to get the display language of.
2154     *
2155     * @return The display language of {@code language}.
2156     *
2157     * @throws NullPointerException if {@code language} is {@code null}.
2158     */
2159    public String getDisplayLanguage( final String language )
2160    {
2161        if ( language == null )
2162        {
2163            throw new NullPointerException( "language" );
2164        }
2165
2166        final Locale l = new Locale( language );
2167        return l.getDisplayLanguage( l );
2168    }
2169
2170    /**
2171     * Formats a calendar instance to a string.
2172     *
2173     * @param calendar The calendar to format to a string.
2174     *
2175     * @return The date of {@code calendar} formatted using a short format style pattern.
2176     *
2177     * @throws NullPointerException if {@code calendar} is {@code null}.
2178     *
2179     * @see DateFormat#SHORT
2180     */
2181    public String getShortDate( final Calendar calendar )
2182    {
2183        if ( calendar == null )
2184        {
2185            throw new NullPointerException( "calendar" );
2186        }
2187
2188        return DateFormat.getDateInstance( DateFormat.SHORT, this.getLocale() ).format( calendar.getTime() );
2189    }
2190
2191    /**
2192     * Formats a calendar instance to a string.
2193     *
2194     * @param calendar The calendar to format to a string.
2195     *
2196     * @return The date of {@code calendar} formatted using a medium format style pattern.
2197     *
2198     * @throws NullPointerException if {@code calendar} is {@code null}.
2199     *
2200     * @see DateFormat#MEDIUM
2201     *
2202     * @since 1.2
2203     */
2204    public String getMediumDate( final Calendar calendar )
2205    {
2206        if ( calendar == null )
2207        {
2208            throw new NullPointerException( "calendar" );
2209        }
2210
2211        return DateFormat.getDateInstance( DateFormat.MEDIUM, this.getLocale() ).format( calendar.getTime() );
2212    }
2213
2214    /**
2215     * Formats a calendar instance to a string.
2216     *
2217     * @param calendar The calendar to format to a string.
2218     *
2219     * @return The date of {@code calendar} formatted using a long format style pattern.
2220     *
2221     * @throws NullPointerException if {@code calendar} is {@code null}.
2222     *
2223     * @see DateFormat#LONG
2224     */
2225    public String getLongDate( final Calendar calendar )
2226    {
2227        if ( calendar == null )
2228        {
2229            throw new NullPointerException( "calendar" );
2230        }
2231
2232        return DateFormat.getDateInstance( DateFormat.LONG, this.getLocale() ).format( calendar.getTime() );
2233    }
2234
2235    /**
2236     * Formats a calendar instance to a string.
2237     *
2238     * @param calendar The calendar to format to a string.
2239     *
2240     * @return The date of {@code calendar} formatted using an ISO-8601 format style.
2241     *
2242     * @throws NullPointerException if {@code calendar} is {@code null}.
2243     *
2244     * @see SimpleDateFormat yyyy-DDD
2245     *
2246     * @since 1.2
2247     */
2248    public String getIsoDate( final Calendar calendar )
2249    {
2250        if ( calendar == null )
2251        {
2252            throw new NullPointerException( "calendar" );
2253        }
2254
2255        return new SimpleDateFormat( "yyyy-DDD", this.getLocale() ).format( calendar.getTime() );
2256    }
2257
2258    /**
2259     * Formats a calendar instance to a string.
2260     *
2261     * @param calendar The calendar to format to a string.
2262     *
2263     * @return The time of {@code calendar} formatted using a short format style pattern.
2264     *
2265     * @throws NullPointerException if {@code calendar} is {@code null}.
2266     *
2267     * @see DateFormat#SHORT
2268     */
2269    public String getShortTime( final Calendar calendar )
2270    {
2271        if ( calendar == null )
2272        {
2273            throw new NullPointerException( "calendar" );
2274        }
2275
2276        return DateFormat.getTimeInstance( DateFormat.SHORT, this.getLocale() ).format( calendar.getTime() );
2277    }
2278
2279    /**
2280     * Formats a calendar instance to a string.
2281     *
2282     * @param calendar The calendar to format to a string.
2283     *
2284     * @return The time of {@code calendar} formatted using a medium format style pattern.
2285     *
2286     * @throws NullPointerException if {@code calendar} is {@code null}.
2287     *
2288     * @see DateFormat#MEDIUM
2289     *
2290     * @since 1.2
2291     */
2292    public String getMediumTime( final Calendar calendar )
2293    {
2294        if ( calendar == null )
2295        {
2296            throw new NullPointerException( "calendar" );
2297        }
2298
2299        return DateFormat.getTimeInstance( DateFormat.MEDIUM, this.getLocale() ).format( calendar.getTime() );
2300    }
2301
2302    /**
2303     * Formats a calendar instance to a string.
2304     *
2305     * @param calendar The calendar to format to a string.
2306     *
2307     * @return The time of {@code calendar} formatted using a long format style pattern.
2308     *
2309     * @throws NullPointerException if {@code calendar} is {@code null}.
2310     *
2311     * @see DateFormat#LONG
2312     */
2313    public String getLongTime( final Calendar calendar )
2314    {
2315        if ( calendar == null )
2316        {
2317            throw new NullPointerException( "calendar" );
2318        }
2319
2320        return DateFormat.getTimeInstance( DateFormat.LONG, this.getLocale() ).format( calendar.getTime() );
2321    }
2322
2323    /**
2324     * Formats a calendar instance to a string.
2325     *
2326     * @param calendar The calendar to format to a string.
2327     *
2328     * @return The time of {@code calendar} formatted using an ISO-8601 format style.
2329     *
2330     * @throws NullPointerException if {@code calendar} is {@code null}.
2331     *
2332     * @see SimpleDateFormat HH:mm
2333     *
2334     * @since 1.2
2335     */
2336    public String getIsoTime( final Calendar calendar )
2337    {
2338        if ( calendar == null )
2339        {
2340            throw new NullPointerException( "calendar" );
2341        }
2342
2343        return new SimpleDateFormat( "HH:mm", this.getLocale() ).format( calendar.getTime() );
2344    }
2345
2346    /**
2347     * Formats a calendar instance to a string.
2348     *
2349     * @param calendar The calendar to format to a string.
2350     *
2351     * @return The date and time of {@code calendar} formatted using a short format style pattern.
2352     *
2353     * @throws NullPointerException if {@code calendar} is {@code null}.
2354     *
2355     * @see DateFormat#SHORT
2356     */
2357    public String getShortDateTime( final Calendar calendar )
2358    {
2359        if ( calendar == null )
2360        {
2361            throw new NullPointerException( "calendar" );
2362        }
2363
2364        return DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT, this.getLocale() ).
2365            format( calendar.getTime() );
2366
2367    }
2368
2369    /**
2370     * Formats a calendar instance to a string.
2371     *
2372     * @param calendar The calendar to format to a string.
2373     *
2374     * @return The date and time of {@code calendar} formatted using a medium format style pattern.
2375     *
2376     * @throws NullPointerException if {@code calendar} is {@code null}.
2377     *
2378     * @see DateFormat#MEDIUM
2379     *
2380     * @since 1.2
2381     */
2382    public String getMediumDateTime( final Calendar calendar )
2383    {
2384        if ( calendar == null )
2385        {
2386            throw new NullPointerException( "calendar" );
2387        }
2388
2389        return DateFormat.getDateTimeInstance( DateFormat.MEDIUM, DateFormat.MEDIUM, this.getLocale() ).
2390            format( calendar.getTime() );
2391
2392    }
2393
2394    /**
2395     * Formats a calendar instance to a string.
2396     *
2397     * @param calendar The calendar to format to a string.
2398     *
2399     * @return The date and time of {@code calendar} formatted using a long format style pattern.
2400     *
2401     * @throws NullPointerException if {@code calendar} is {@code null}.
2402     *
2403     * @see DateFormat#LONG
2404     */
2405    public String getLongDateTime( final Calendar calendar )
2406    {
2407        if ( calendar == null )
2408        {
2409            throw new NullPointerException( "calendar" );
2410        }
2411
2412        return DateFormat.getDateTimeInstance( DateFormat.LONG, DateFormat.LONG, this.getLocale() ).
2413            format( calendar.getTime() );
2414
2415    }
2416
2417    /**
2418     * Formats a calendar instance to a string.
2419     *
2420     * @param calendar The calendar to format to a string.
2421     *
2422     * @return The date and time of {@code calendar} formatted using a ISO-8601 format style.
2423     *
2424     * @throws NullPointerException if {@code calendar} is {@code null}.
2425     *
2426     * @see SimpleDateFormat yyyy-MM-dd'T'HH:mm:ssZ
2427     *
2428     * @since 1.2
2429     */
2430    public String getIsoDateTime( final Calendar calendar )
2431    {
2432        if ( calendar == null )
2433        {
2434            throw new NullPointerException( "calendar" );
2435        }
2436
2437        return Closeable.class.isAssignableFrom( ClassLoader.class )
2438                   ? new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ssXXX", this.getLocale() ).format( calendar.getTime() )
2439                   : new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ssZ", this.getLocale() ).format( calendar.getTime() );
2440
2441    }
2442
2443    /**
2444     * Gets a string describing the range of years for given calendars.
2445     *
2446     * @param start The start of the range.
2447     * @param end The end of the range.
2448     *
2449     * @return Formatted range of the years of {@code start} and {@code end} (e.g. {@code "start - end"}).
2450     *
2451     * @throws NullPointerException if {@code start} or {@code end} is {@code null}.
2452     */
2453    public String getYears( final Calendar start, final Calendar end )
2454    {
2455        if ( start == null )
2456        {
2457            throw new NullPointerException( "start" );
2458        }
2459        if ( end == null )
2460        {
2461            throw new NullPointerException( "end" );
2462        }
2463
2464        final Format yearFormat = new SimpleDateFormat( "yyyy", this.getLocale() );
2465        final int s = start.get( Calendar.YEAR );
2466        final int e = end.get( Calendar.YEAR );
2467        final StringBuilder years = new StringBuilder();
2468
2469        if ( s != e )
2470        {
2471            if ( s < e )
2472            {
2473                years.append( yearFormat.format( start.getTime() ) ).append( " - " ).
2474                    append( yearFormat.format( end.getTime() ) );
2475
2476            }
2477            else
2478            {
2479                years.append( yearFormat.format( end.getTime() ) ).append( " - " ).
2480                    append( yearFormat.format( start.getTime() ) );
2481
2482            }
2483        }
2484        else
2485        {
2486            years.append( yearFormat.format( start.getTime() ) );
2487        }
2488
2489        return years.toString();
2490    }
2491
2492    /**
2493     * Gets the model of the instance.
2494     *
2495     * @return The model of the instance.
2496     *
2497     * @see #getModules()
2498     * @see #setModel(org.jomc.modlet.Model)
2499     */
2500    public final Model getModel()
2501    {
2502        if ( this.model == null )
2503        {
2504            this.model = new Model();
2505            this.model.setIdentifier( ModelObject.MODEL_PUBLIC_ID );
2506        }
2507
2508        return this.model;
2509    }
2510
2511    /**
2512     * Sets the model of the instance.
2513     *
2514     * @param value The new model of the instance or {@code null}.
2515     *
2516     * @see #getModel()
2517     */
2518    public final void setModel( final Model value )
2519    {
2520        this.model = value;
2521    }
2522
2523    /**
2524     * Gets the modules of the model of the instance.
2525     *
2526     * @return The modules of the model of the instance or {@code null}, if no modules are found.
2527     *
2528     * @see #getModel()
2529     * @see #setModel(org.jomc.modlet.Model)
2530     */
2531    public final Modules getModules()
2532    {
2533        return ModelHelper.getModules( this.getModel() );
2534    }
2535
2536    /**
2537     * Gets the {@code VelocityEngine} of the instance.
2538     *
2539     * @return The {@code VelocityEngine} of the instance.
2540     *
2541     * @see #setVelocityEngine(org.apache.velocity.app.VelocityEngine)
2542     */
2543    public final VelocityEngine getVelocityEngine()
2544    {
2545        if ( this.velocityEngine == null )
2546        {
2547            /**
2548             * {@code LogChute} logging to the listeners of the tool.
2549             */
2550            class JomcLogChute implements LogChute
2551            {
2552
2553                JomcLogChute()
2554                {
2555                    super();
2556                }
2557
2558                public void init( final RuntimeServices runtimeServices ) throws Exception
2559                {
2560                }
2561
2562                public void log( final int level, final String message )
2563                {
2564                    this.log( level, message, null );
2565                }
2566
2567                public void log( final int level, final String message, final Throwable throwable )
2568                {
2569                    JomcTool.this.log( Level.FINEST, message, throwable );
2570                }
2571
2572                public boolean isLevelEnabled( final int level )
2573                {
2574                    return isLoggable( Level.FINEST );
2575                }
2576
2577            }
2578
2579            final VelocityEngine engine = new VelocityEngine();
2580            engine.setProperty( RuntimeConstants.RUNTIME_REFERENCES_STRICT, Boolean.TRUE.toString() );
2581            engine.setProperty( RuntimeConstants.VM_ARGUMENTS_STRICT, Boolean.TRUE.toString() );
2582            engine.setProperty( RuntimeConstants.STRICT_MATH, Boolean.TRUE.toString() );
2583            engine.setProperty( RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new JomcLogChute() );
2584
2585            engine.setProperty( RuntimeConstants.RESOURCE_LOADER, "class" );
2586            engine.setProperty( "class.resource.loader.class", ClasspathResourceLoader.class.getName() );
2587            engine.setProperty( "class.resource.loader.cache", Boolean.TRUE.toString() );
2588
2589            if ( this.getTemplateLocation() != null )
2590            {
2591                engine.setProperty( RuntimeConstants.RESOURCE_LOADER, "class,url" );
2592                engine.setProperty( "url.resource.loader.class", URLResourceLoader.class.getName() );
2593                engine.setProperty( "url.resource.loader.cache", Boolean.TRUE.toString() );
2594                engine.setProperty( "url.resource.loader.root", this.getTemplateLocation().toExternalForm() );
2595                engine.setProperty( "url.resource.loader.timeout", Integer.toString( 60000 ) );
2596            }
2597
2598            this.velocityEngine = engine;
2599            this.defaultVelocityEngine = true;
2600        }
2601
2602        return this.velocityEngine;
2603    }
2604
2605    /**
2606     * Sets the {@code VelocityEngine} of the instance.
2607     *
2608     * @param value The new {@code VelocityEngine} of the instance or {@code null}.
2609     *
2610     * @see #getVelocityEngine()
2611     */
2612    public final void setVelocityEngine( final VelocityEngine value )
2613    {
2614        this.velocityEngine = value;
2615        this.defaultVelocityEngine = false;
2616    }
2617
2618    /**
2619     * Gets a new velocity context used for merging templates.
2620     *
2621     * @return A new velocity context used for merging templates.
2622     *
2623     * @throws IOException if creating a new context instance fails.
2624     *
2625     * @see #getTemplateParameters()
2626     */
2627    public VelocityContext getVelocityContext() throws IOException
2628    {
2629        final Calendar now = Calendar.getInstance();
2630        final VelocityContext ctx =
2631            new VelocityContext( new HashMap<String, Object>( this.getTemplateParameters() ) );
2632
2633        this.mergeTemplateProfileContextProperties( this.getTemplateProfile(), this.getLocale().getLanguage(), ctx );
2634        this.mergeTemplateProfileContextProperties( this.getTemplateProfile(), null, ctx );
2635
2636        final Model clonedModel = this.getModel().clone();
2637        final Modules clonedModules = ModelHelper.getModules( clonedModel );
2638        assert clonedModules != null : "Unexpected missing modules for model '" + clonedModel.getIdentifier() + "'.";
2639
2640        ctx.put( "model", clonedModel );
2641        ctx.put( "modules", clonedModules );
2642        ctx.put( "imodel", new InheritanceModel( clonedModules ) );
2643        ctx.put( "tool", this );
2644        ctx.put( "toolName", this.getClass().getName() );
2645        ctx.put( "toolVersion", getMessage( "projectVersion" ) );
2646        ctx.put( "toolUrl", getMessage( "projectUrl" ) );
2647        ctx.put( "calendar", now.getTime() );
2648        ctx.put( "now",
2649                 Closeable.class.isAssignableFrom( ClassLoader.class )
2650                     ? new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", this.getLocale() ).format( now.getTime() )
2651                     : new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ", this.getLocale() ).format( now.getTime() ) );
2652
2653        ctx.put( "year", new SimpleDateFormat( "yyyy", this.getLocale() ).format( now.getTime() ) );
2654        ctx.put( "month", new SimpleDateFormat( "MM", this.getLocale() ).format( now.getTime() ) );
2655        ctx.put( "day", new SimpleDateFormat( "dd", this.getLocale() ).format( now.getTime() ) );
2656        ctx.put( "hour", new SimpleDateFormat( "HH", this.getLocale() ).format( now.getTime() ) );
2657        ctx.put( "minute", new SimpleDateFormat( "mm", this.getLocale() ).format( now.getTime() ) );
2658        ctx.put( "second", new SimpleDateFormat( "ss", this.getLocale() ).format( now.getTime() ) );
2659        ctx.put( "timezone",
2660                 Closeable.class.isAssignableFrom( ClassLoader.class )
2661                     ? new SimpleDateFormat( "XXX", this.getLocale() ).format( now.getTime() )
2662                     : new SimpleDateFormat( "Z", this.getLocale() ).format( now.getTime() ) );
2663
2664        ctx.put( "shortDate", this.getShortDate( now ) );
2665        ctx.put( "mediumDate", this.getMediumDate( now ) );
2666        ctx.put( "longDate", this.getLongDate( now ) );
2667        ctx.put( "isoDate", this.getIsoDate( now ) );
2668        ctx.put( "shortTime", this.getShortTime( now ) );
2669        ctx.put( "mediumTime", this.getMediumTime( now ) );
2670        ctx.put( "longTime", this.getLongTime( now ) );
2671        ctx.put( "isoTime", this.getIsoTime( now ) );
2672        ctx.put( "shortDateTime", this.getShortDateTime( now ) );
2673        ctx.put( "mediumDateTime", this.getMediumDateTime( now ) );
2674        ctx.put( "longDateTime", this.getLongDateTime( now ) );
2675        ctx.put( "isoDateTime", this.getIsoDateTime( now ) );
2676
2677        return ctx;
2678    }
2679
2680    /**
2681     * Gets the template parameters of the instance.
2682     * <p>
2683     * This accessor method returns a reference to the live map, not a snapshot. Therefore any modification you make
2684     * to the returned map will be present inside the object. This is why there is no {@code set} method for the
2685     * template parameters property.
2686     * </p>
2687     *
2688     * @return The template parameters of the instance.
2689     *
2690     * @see #getVelocityContext()
2691     *
2692     * @since 1.2
2693     */
2694    public final Map<String, Object> getTemplateParameters()
2695    {
2696        return this.templateParameters;
2697    }
2698
2699    /**
2700     * Gets the location to search for templates in addition to searching the class path.
2701     *
2702     * @return The location to search for templates in addition to searching the class path or {@code null}.
2703     *
2704     * @see #setTemplateLocation(java.net.URL)
2705     *
2706     * @since 1.2
2707     */
2708    public final URL getTemplateLocation()
2709    {
2710        return this.templateLocation;
2711    }
2712
2713    /**
2714     * Sets the location to search for templates in addition to searching the class path.
2715     *
2716     * @param value The new location to search for templates in addition to searching the class path or {@code null}.
2717     *
2718     * @see #getTemplateLocation()
2719     *
2720     * @since 1.2
2721     */
2722    public final void setTemplateLocation( final URL value )
2723    {
2724        this.templateLocation = value;
2725        this.templateProfileContextPropertiesCache = null;
2726        this.templateProfilePropertiesCache = null;
2727
2728        if ( this.defaultVelocityEngine )
2729        {
2730            this.setVelocityEngine( null );
2731        }
2732    }
2733
2734    /**
2735     * Gets the encoding to use for reading templates.
2736     *
2737     * @return The encoding to use for reading templates.
2738     *
2739     * @see #setTemplateEncoding(java.lang.String)
2740     *
2741     * @deprecated As of JOMC 1.3, replaced by method {@link #getDefaultTemplateEncoding()}. This method will be removed
2742     * in JOMC 2.0.
2743     */
2744    @Deprecated
2745    public final String getTemplateEncoding()
2746    {
2747        return this.getDefaultTemplateEncoding();
2748    }
2749
2750    /**
2751     * Sets the encoding to use for reading templates.
2752     *
2753     * @param value The new encoding to use for reading templates or {@code null}.
2754     *
2755     * @see #getTemplateEncoding()
2756     *
2757     * @deprecated As of JOMC 1.3, replaced by method {@link #setDefaultTemplateEncoding(java.lang.String)}. This method
2758     * will be removed in JOMC 2.0.
2759     */
2760    @Deprecated
2761    public final void setTemplateEncoding( final String value )
2762    {
2763        this.setDefaultTemplateEncoding( value );
2764    }
2765
2766    /**
2767     * Gets the default encoding used for reading templates.
2768     *
2769     * @return The default encoding used for reading templates.
2770     *
2771     * @see #setDefaultTemplateEncoding(java.lang.String)
2772     *
2773     * @since 1.3
2774     */
2775    public final String getDefaultTemplateEncoding()
2776    {
2777        if ( this.defaultTemplateEncoding == null )
2778        {
2779            this.defaultTemplateEncoding = getMessage( "buildSourceEncoding" );
2780
2781            if ( this.isLoggable( Level.CONFIG ) )
2782            {
2783                this.log( Level.CONFIG, getMessage( "defaultTemplateEncoding", this.defaultTemplateEncoding ), null );
2784            }
2785        }
2786
2787        return this.defaultTemplateEncoding;
2788    }
2789
2790    /**
2791     * Sets the default encoding to use for reading templates.
2792     *
2793     * @param value The new default encoding to use for reading templates or {@code null}.
2794     *
2795     * @see #getDefaultTemplateEncoding()
2796     *
2797     * @since 1.3
2798     */
2799    public final void setDefaultTemplateEncoding( final String value )
2800    {
2801        this.defaultTemplateEncoding = value;
2802        this.templateCache = null;
2803    }
2804
2805    /**
2806     * Gets the template encoding of a given template profile.
2807     *
2808     * @param tp The template profile to get the template encoding of.
2809     *
2810     * @return The template encoding of the template profile identified by {@code tp} or the default template encoding
2811     * if no such encoding is defined.
2812     *
2813     * @throws NullPointerException if {@code tp} is {@code null}.
2814     *
2815     * @see #getDefaultTemplateEncoding()
2816     *
2817     * @since 1.3
2818     */
2819    public final String getTemplateEncoding( final String tp )
2820    {
2821        if ( tp == null )
2822        {
2823            throw new NullPointerException( "tp" );
2824        }
2825
2826        String te = null;
2827
2828        try
2829        {
2830            te = this.getTemplateProfileProperties( tp ).getProperty( TEMPLATE_ENCODING_PROFILE_PROPERTY_NAME );
2831        }
2832        catch ( final IOException e )
2833        {
2834            if ( this.isLoggable( Level.SEVERE ) )
2835            {
2836                this.log( Level.SEVERE, getMessage( e ), e );
2837            }
2838        }
2839
2840        return te != null ? te : this.getDefaultTemplateEncoding();
2841    }
2842
2843    /**
2844     * Gets the encoding to use for reading files.
2845     *
2846     * @return The encoding to use for reading files.
2847     *
2848     * @see #setInputEncoding(java.lang.String)
2849     */
2850    public final String getInputEncoding()
2851    {
2852        if ( this.inputEncoding == null )
2853        {
2854            this.inputEncoding = new InputStreamReader( new ByteArrayInputStream( NO_BYTES ) ).getEncoding();
2855
2856            if ( this.isLoggable( Level.CONFIG ) )
2857            {
2858                this.log( Level.CONFIG, getMessage( "defaultInputEncoding", this.inputEncoding ), null );
2859            }
2860        }
2861
2862        return this.inputEncoding;
2863    }
2864
2865    /**
2866     * Sets the encoding to use for reading files.
2867     *
2868     * @param value The new encoding to use for reading files or {@code null}.
2869     *
2870     * @see #getInputEncoding()
2871     */
2872    public final void setInputEncoding( final String value )
2873    {
2874        this.inputEncoding = value;
2875    }
2876
2877    /**
2878     * Gets the encoding to use for writing files.
2879     *
2880     * @return The encoding to use for writing files.
2881     *
2882     * @see #setOutputEncoding(java.lang.String)
2883     */
2884    public final String getOutputEncoding()
2885    {
2886        if ( this.outputEncoding == null )
2887        {
2888            this.outputEncoding = new OutputStreamWriter( new ByteArrayOutputStream() ).getEncoding();
2889
2890            if ( this.isLoggable( Level.CONFIG ) )
2891            {
2892                this.log( Level.CONFIG, getMessage( "defaultOutputEncoding", this.outputEncoding ), null );
2893            }
2894        }
2895
2896        return this.outputEncoding;
2897    }
2898
2899    /**
2900     * Sets the encoding to use for writing files.
2901     *
2902     * @param value The encoding to use for writing files or {@code null}.
2903     *
2904     * @see #getOutputEncoding()
2905     */
2906    public final void setOutputEncoding( final String value )
2907    {
2908        this.outputEncoding = value;
2909    }
2910
2911    /**
2912     * Gets the default template profile.
2913     * <p>
2914     * The default template profile is the implicit parent profile of any template profile not specifying a parent
2915     * template profile.
2916     * </p>
2917     *
2918     * @return The default template profile.
2919     *
2920     * @see #setDefaultTemplateProfile(java.lang.String)
2921     *
2922     * @deprecated The {@code static} modifier of this method and support to setup the default template profile using
2923     * a system property will be removed in version 2.0.
2924     */
2925    @Deprecated
2926    public static String getDefaultTemplateProfile()
2927    {
2928        if ( defaultTemplateProfile == null )
2929        {
2930            defaultTemplateProfile = System.getProperty( "org.jomc.tools.JomcTool.defaultTemplateProfile",
2931                                                         DEFAULT_TEMPLATE_PROFILE );
2932
2933        }
2934
2935        return defaultTemplateProfile;
2936    }
2937
2938    /**
2939     * Sets the default template profile.
2940     *
2941     * @param value The new default template profile or {@code null}.
2942     *
2943     * @see #getDefaultTemplateProfile()
2944     *
2945     * @deprecated The {@code static} modifier of this method will be removed in version 2.0.
2946     */
2947    @Deprecated
2948    public static void setDefaultTemplateProfile( final String value )
2949    {
2950        defaultTemplateProfile = value;
2951    }
2952
2953    /**
2954     * Gets the template profile of the instance.
2955     *
2956     * @return The template profile of the instance.
2957     *
2958     * @see #getDefaultTemplateProfile()
2959     * @see #setTemplateProfile(java.lang.String)
2960     */
2961    public final String getTemplateProfile()
2962    {
2963        if ( this.templateProfile == null )
2964        {
2965            this.templateProfile = getDefaultTemplateProfile();
2966
2967            if ( this.isLoggable( Level.CONFIG ) )
2968            {
2969                this.log( Level.CONFIG, getMessage( "defaultTemplateProfile", this.templateProfile ), null );
2970            }
2971        }
2972
2973        return this.templateProfile;
2974    }
2975
2976    /**
2977     * Sets the template profile of the instance.
2978     *
2979     * @param value The new template profile of the instance or {@code null}.
2980     *
2981     * @see #getTemplateProfile()
2982     */
2983    public final void setTemplateProfile( final String value )
2984    {
2985        this.templateProfile = value;
2986    }
2987
2988    /**
2989     * Gets the parent template profile of a given template profile.
2990     *
2991     * @param tp The template profile to get the parent template profile of.
2992     *
2993     * @return The parent template profile of the template profile identified by {@code tp}; the default template
2994     * profile, if no such parent template profile is defined; {@code null}, if {@code tp} denotes the default template
2995     * profile.
2996     *
2997     * @throws NullPointerException if {@code tp} is {@code null}.
2998     *
2999     * @see #getDefaultTemplateProfile()
3000     *
3001     * @since 1.3
3002     */
3003    public final String getParentTemplateProfile( final String tp )
3004    {
3005        if ( tp == null )
3006        {
3007            throw new NullPointerException( "tp" );
3008        }
3009
3010        String parentTemplateProfile = null;
3011
3012        try
3013        {
3014            parentTemplateProfile =
3015                this.getTemplateProfileProperties( tp ).getProperty( PARENT_TEMPLATE_PROFILE_PROPERTY_NAME );
3016
3017        }
3018        catch ( final IOException e )
3019        {
3020            if ( this.isLoggable( Level.SEVERE ) )
3021            {
3022                this.log( Level.SEVERE, getMessage( e ), e );
3023            }
3024        }
3025
3026        return parentTemplateProfile != null ? parentTemplateProfile
3027                   : tp.equals( this.getDefaultTemplateProfile() ) ? null : this.getDefaultTemplateProfile();
3028
3029    }
3030
3031    /**
3032     * Gets the indentation string of the instance.
3033     *
3034     * @return The indentation string of the instance.
3035     *
3036     * @see #setIndentation(java.lang.String)
3037     */
3038    public final String getIndentation()
3039    {
3040        if ( this.indentation == null )
3041        {
3042            this.indentation = "    ";
3043
3044            if ( this.isLoggable( Level.CONFIG ) )
3045            {
3046                this.log( Level.CONFIG, getMessage( "defaultIndentation",
3047                                                    StringEscapeUtils.escapeJava( this.indentation ) ), null );
3048
3049            }
3050        }
3051
3052        return this.indentation;
3053    }
3054
3055    /**
3056     * Gets an indentation string for a given indentation level.
3057     *
3058     * @param level The indentation level to get an indentation string for.
3059     *
3060     * @return The indentation string for {@code level}.
3061     *
3062     * @throws IllegalArgumentException if {@code level} is negative.
3063     *
3064     * @see #getIndentation()
3065     */
3066    public final String getIndentation( final int level )
3067    {
3068        if ( level < 0 )
3069        {
3070            throw new IllegalArgumentException( Integer.toString( level ) );
3071        }
3072
3073        Map<String, String> map = this.indentationCache == null ? null : this.indentationCache.get();
3074
3075        if ( map == null )
3076        {
3077            map = new ConcurrentHashMap<String, String>( 8 );
3078            this.indentationCache = new SoftReference<Map<String, String>>( map );
3079        }
3080
3081        final String key = this.getIndentation() + "|" + level;
3082        String idt = map.get( key );
3083
3084        if ( idt == null )
3085        {
3086            final StringBuilder b = new StringBuilder( this.getIndentation().length() * level );
3087
3088            for ( int i = level; i > 0; i-- )
3089            {
3090                b.append( this.getIndentation() );
3091            }
3092
3093            idt = b.toString();
3094            map.put( key, idt );
3095        }
3096
3097        return idt;
3098    }
3099
3100    /**
3101     * Sets the indentation string of the instance.
3102     *
3103     * @param value The new indentation string of the instance or {@code null}.
3104     *
3105     * @see #getIndentation()
3106     */
3107    public final void setIndentation( final String value )
3108    {
3109        this.indentation = value;
3110    }
3111
3112    /**
3113     * Gets the line separator of the instance.
3114     *
3115     * @return The line separator of the instance.
3116     *
3117     * @see #setLineSeparator(java.lang.String)
3118     */
3119    public final String getLineSeparator()
3120    {
3121        if ( this.lineSeparator == null )
3122        {
3123            this.lineSeparator = System.getProperty( "line.separator", "\n" );
3124
3125            if ( this.isLoggable( Level.CONFIG ) )
3126            {
3127                this.log( Level.CONFIG, getMessage( "defaultLineSeparator",
3128                                                    StringEscapeUtils.escapeJava( this.lineSeparator ) ), null );
3129
3130            }
3131        }
3132
3133        return this.lineSeparator;
3134    }
3135
3136    /**
3137     * Sets the line separator of the instance.
3138     *
3139     * @param value The new line separator of the instance or {@code null}.
3140     *
3141     * @see #getLineSeparator()
3142     */
3143    public final void setLineSeparator( final String value )
3144    {
3145        this.lineSeparator = value;
3146    }
3147
3148    /**
3149     * Gets the locale of the instance.
3150     *
3151     * @return The locale of the instance.
3152     *
3153     * @see #setLocale(java.util.Locale)
3154     *
3155     * @since 1.2
3156     */
3157    public final Locale getLocale()
3158    {
3159        if ( this.locale == null )
3160        {
3161            this.locale = Locale.ENGLISH;
3162
3163            if ( this.isLoggable( Level.CONFIG ) )
3164            {
3165                this.log( Level.CONFIG, getMessage( "defaultLocale", this.locale ), null );
3166            }
3167        }
3168
3169        return this.locale;
3170    }
3171
3172    /**
3173     * Sets the locale of the instance.
3174     *
3175     * @param value The new locale of the instance or {@code null}.
3176     *
3177     * @see #getLocale()
3178     *
3179     * @since 1.2
3180     */
3181    public final void setLocale( final Locale value )
3182    {
3183        this.locale = value;
3184    }
3185
3186    /**
3187     * Gets an {@code ExecutorService} used to run tasks in parallel.
3188     *
3189     * @return An {@code ExecutorService} used to run tasks in parallel or {@code null}, if no such service has been
3190     * provided by an application.
3191     *
3192     * @since 1.10
3193     *
3194     * @see #setExecutorService(java.util.concurrent.ExecutorService)
3195     */
3196    public final ExecutorService getExecutorService()
3197    {
3198        return this.executorService;
3199    }
3200
3201    /**
3202     * Sets the {@code ExecutorService} to be used to run tasks in parallel.
3203     * <p>
3204     * The {@code ExecutorService} to be used to run tasks in parallel is an optional entity. If no such service is
3205     * provided by an application, no parallelization is performed. Configuration or lifecycle management of the given
3206     * {@code ExecutorService} is the responsibility of the application.
3207     * </p>
3208     *
3209     * @param value The {@code ExecutorService} to be used to run tasks in parallel or {@code null}, to disable any
3210     * parallelization.
3211     *
3212     * @since 1.10
3213     *
3214     * @see #getExecutorService()
3215     */
3216    public final void setExecutorService( final ExecutorService value )
3217    {
3218        this.executorService = value;
3219        if ( this.executorService != null )
3220        {
3221            this.initDefaults();
3222        }
3223    }
3224
3225    /**
3226     * Gets a velocity template for a given name.
3227     * <p>
3228     * This method searches templates at the following locations recursively in the shown order stopping whenever
3229     * a matching template is found.
3230     * <ol>
3231     * <li><code>org/jomc/tools/templates/{@link #getTemplateProfile() profile}/{@link #getLocale() language}/<i>templateName</i></code></li>
3232     * <li><code>org/jomc/tools/templates/{@link #getParentTemplateProfile(java.lang.String) parent profile}/{@link #getLocale() language}/<i>templateName</i></code></li>
3233     * <li><code>org/jomc/tools/templates/{@link #getTemplateProfile() profile}/<i>templateName</i></code></li>
3234     * <li><code>org/jomc/tools/templates/{@link #getParentTemplateProfile(java.lang.String) parent profile}/{@link #getLocale() language}/<i>templateName</i></code></li>
3235     * </ol></p>
3236     *
3237     * @param templateName The name of the template to get.
3238     *
3239     * @return The template matching {@code templateName}.
3240     *
3241     * @throws NullPointerException if {@code templateName} is {@code null}.
3242     * @throws FileNotFoundException if no such template is found.
3243     * @throws IOException if getting the template fails.
3244     *
3245     * @see #getTemplateProfile()
3246     * @see #getParentTemplateProfile(java.lang.String)
3247     * @see #getLocale()
3248     * @see #getTemplateEncoding(java.lang.String)
3249     * @see #getVelocityEngine()
3250     */
3251    public Template getVelocityTemplate( final String templateName ) throws FileNotFoundException, IOException
3252    {
3253        if ( templateName == null )
3254        {
3255            throw new NullPointerException( "templateName" );
3256        }
3257
3258        return this.getVelocityTemplate( this.getTemplateProfile(), templateName );
3259    }
3260
3261    /**
3262     * Notifies registered listeners.
3263     *
3264     * @param level The level of the event.
3265     * @param message The message of the event or {@code null}.
3266     * @param throwable The throwable of the event or {@code null}.
3267     *
3268     * @throws NullPointerException if {@code level} is {@code null}.
3269     *
3270     * @see #getListeners()
3271     * @see #isLoggable(java.util.logging.Level)
3272     */
3273    public void log( final Level level, final String message, final Throwable throwable )
3274    {
3275        if ( level == null )
3276        {
3277            throw new NullPointerException( "level" );
3278        }
3279
3280        if ( this.isLoggable( level ) )
3281        {
3282            for ( final Listener listener : this.getListeners() )
3283            {
3284                listener.onLog( level, message, throwable );
3285            }
3286        }
3287    }
3288
3289    void initDefaults()
3290    {
3291        this.getLogLevel();
3292        this.getModel();
3293        this.getVelocityEngine();
3294        this.getDefaultTemplateEncoding();
3295        this.getInputEncoding();
3296        this.getOutputEncoding();
3297        this.getTemplateProfile();
3298        this.getIndentation();
3299        this.getLineSeparator();
3300        this.getLocale();
3301    }
3302
3303    private Template findVelocityTemplate( final String location, final String encoding ) throws IOException
3304    {
3305        try
3306        {
3307            return this.getVelocityEngine().getTemplate( location, encoding );
3308        }
3309        catch ( final ResourceNotFoundException e )
3310        {
3311            if ( this.isLoggable( Level.FINER ) )
3312            {
3313                this.log( Level.FINER, getMessage( "templateNotFound", location ), null );
3314            }
3315
3316            return null;
3317        }
3318        catch ( final ParseErrorException e )
3319        {
3320            String m = getMessage( e );
3321            m = m == null ? "" : " " + m;
3322
3323            // JDK: As of JDK 6, "new IOException( message, cause )".
3324            throw (IOException) new IOException( getMessage( "invalidTemplate", location, m ) ).initCause( e );
3325        }
3326        catch ( final VelocityException e )
3327        {
3328            String m = getMessage( e );
3329            m = m == null ? "" : " " + m;
3330
3331            // JDK: As of JDK 6, "new IOException( message, cause )".
3332            throw (IOException) new IOException( getMessage( "velocityException", location, m ) ).initCause( e );
3333        }
3334    }
3335
3336    private java.util.Properties getTemplateProfileContextProperties( final String profileName, final String language )
3337        throws IOException
3338    {
3339        Map<String, java.util.Properties> map = this.templateProfileContextPropertiesCache == null
3340                                                    ? null : this.templateProfileContextPropertiesCache.get();
3341
3342        if ( map == null )
3343        {
3344            map = new ConcurrentHashMap<String, java.util.Properties>();
3345            this.templateProfileContextPropertiesCache = new SoftReference<Map<String, java.util.Properties>>( map );
3346        }
3347
3348        final String key = profileName + "|" + language;
3349        java.util.Properties profileProperties = map.get( key );
3350
3351        if ( profileProperties == null )
3352        {
3353            InputStream in = null;
3354            URL url = null;
3355            profileProperties = new java.util.Properties();
3356
3357            final String resourceName = TEMPLATE_PREFIX + profileName + ( language == null ? "" : "/" + language )
3358                                            + "/context.properties";
3359
3360            try
3361            {
3362                url = this.getClass().getResource( "/" + resourceName );
3363
3364                if ( url != null )
3365                {
3366                    in = url.openStream();
3367
3368                    if ( this.isLoggable( Level.CONFIG ) )
3369                    {
3370                        this.log( Level.CONFIG, getMessage( "contextPropertiesFound", url.toExternalForm() ), null );
3371                    }
3372
3373                    profileProperties.load( in );
3374
3375                    in.close();
3376                    in = null;
3377                }
3378                else if ( this.getTemplateLocation() != null )
3379                {
3380                    if ( this.isLoggable( Level.CONFIG ) )
3381                    {
3382                        this.log( Level.CONFIG, getMessage( "contextPropertiesNotFound", resourceName ), null );
3383                    }
3384
3385                    url = new URL( this.getTemplateLocation(), resourceName );
3386                    in = url.openStream();
3387
3388                    if ( this.isLoggable( Level.CONFIG ) )
3389                    {
3390                        this.log( Level.CONFIG, getMessage( "contextPropertiesFound", url.toExternalForm() ), null );
3391                    }
3392
3393                    profileProperties.load( in );
3394
3395                    in.close();
3396                    in = null;
3397                }
3398                else if ( this.isLoggable( Level.CONFIG ) )
3399                {
3400                    this.log( Level.CONFIG, getMessage( "contextPropertiesNotFound", resourceName ), null );
3401                }
3402            }
3403            catch ( final FileNotFoundException e )
3404            {
3405                if ( this.isLoggable( Level.CONFIG ) )
3406                {
3407                    this.log( Level.CONFIG, getMessage( "contextPropertiesNotFound", url.toExternalForm() ), null );
3408                }
3409            }
3410            finally
3411            {
3412                map.put( key, profileProperties );
3413
3414                try
3415                {
3416                    if ( in != null )
3417                    {
3418                        in.close();
3419                    }
3420                }
3421                catch ( final IOException e )
3422                {
3423                    this.log( Level.SEVERE, getMessage( e ), e );
3424                }
3425            }
3426        }
3427
3428        return profileProperties;
3429    }
3430
3431    private void mergeTemplateProfileContextProperties( final String profileName, final String language,
3432                                                        final VelocityContext velocityContext ) throws IOException
3433    {
3434        if ( profileName != null )
3435        {
3436            final java.util.Properties templateProfileProperties =
3437                this.getTemplateProfileContextProperties( profileName, language );
3438
3439            for ( final Enumeration<?> e = templateProfileProperties.propertyNames(); e.hasMoreElements(); )
3440            {
3441                final String name = e.nextElement().toString();
3442                final String value = templateProfileProperties.getProperty( name );
3443                final String[] values = value.split( "\\|" );
3444
3445                if ( !velocityContext.containsKey( name ) )
3446                {
3447                    final String className = values[0];
3448
3449                    try
3450                    {
3451                        if ( values.length > 1 )
3452                        {
3453                            final Class<?> valueClass = Class.forName( className );
3454                            velocityContext.put( name,
3455                                                 valueClass.getConstructor( String.class ).newInstance( values[1] ) );
3456                        }
3457                        else if ( value.contains( "|" ) )
3458                        {
3459                            velocityContext.put( name, Class.forName( values[0] ).newInstance() );
3460                        }
3461                        else
3462                        {
3463                            velocityContext.put( name, value );
3464                        }
3465                    }
3466                    catch ( final InstantiationException ex )
3467                    {
3468                        // JDK: As of JDK 6, "new IOException( message, cause )".
3469                        throw (IOException) new IOException( getMessage(
3470                            "contextPropertiesException", profileName + ( language != null ? ", " + language : "" ) ) ).
3471                            initCause( ex );
3472
3473                    }
3474                    catch ( final IllegalAccessException ex )
3475                    {
3476                        // JDK: As of JDK 6, "new IOException( message, cause )".
3477                        throw (IOException) new IOException( getMessage(
3478                            "contextPropertiesException", profileName + ( language != null ? ", " + language : "" ) ) ).
3479                            initCause( ex );
3480
3481                    }
3482                    catch ( final InvocationTargetException ex )
3483                    {
3484                        // JDK: As of JDK 6, "new IOException( message, cause )".
3485                        throw (IOException) new IOException( getMessage(
3486                            "contextPropertiesException", profileName + ( language != null ? ", " + language : "" ) ) ).
3487                            initCause( ex );
3488
3489                    }
3490                    catch ( final NoSuchMethodException ex )
3491                    {
3492                        // JDK: As of JDK 6, "new IOException( message, cause )".
3493                        throw (IOException) new IOException( getMessage(
3494                            "contextPropertiesException", profileName + ( language != null ? ", " + language : "" ) ) ).
3495                            initCause( ex );
3496
3497                    }
3498                    catch ( final ClassNotFoundException ex )
3499                    {
3500                        // JDK: As of JDK 6, "new IOException( message, cause )".
3501                        throw (IOException) new IOException( getMessage(
3502                            "contextPropertiesException", profileName + ( language != null ? ", " + language : "" ) ) ).
3503                            initCause( ex );
3504
3505                    }
3506                }
3507            }
3508
3509            this.mergeTemplateProfileContextProperties( this.getParentTemplateProfile( profileName ), language,
3510                                                        velocityContext );
3511
3512        }
3513    }
3514
3515    private java.util.Properties getTemplateProfileProperties( final String profileName ) throws IOException
3516    {
3517        Map<String, java.util.Properties> map = this.templateProfilePropertiesCache == null
3518                                                    ? null : this.templateProfilePropertiesCache.get();
3519
3520        if ( map == null )
3521        {
3522            map = new ConcurrentHashMap<String, java.util.Properties>();
3523            this.templateProfilePropertiesCache = new SoftReference<Map<String, java.util.Properties>>( map );
3524        }
3525
3526        java.util.Properties profileProperties = map.get( profileName );
3527
3528        if ( profileProperties == null )
3529        {
3530            InputStream in = null;
3531            profileProperties = new java.util.Properties();
3532
3533            final String resourceName = TEMPLATE_PREFIX + profileName + "/profile.properties";
3534            URL url = null;
3535
3536            try
3537            {
3538                url = this.getClass().getResource( "/" + resourceName );
3539
3540                if ( url != null )
3541                {
3542                    in = url.openStream();
3543
3544                    if ( this.isLoggable( Level.CONFIG ) )
3545                    {
3546                        this.log( Level.CONFIG, getMessage( "templateProfilePropertiesFound", url.toExternalForm() ),
3547                                  null );
3548
3549                    }
3550
3551                    profileProperties.load( in );
3552
3553                    in.close();
3554                    in = null;
3555                }
3556                else if ( this.getTemplateLocation() != null )
3557                {
3558                    if ( this.isLoggable( Level.CONFIG ) )
3559                    {
3560                        this.log( Level.CONFIG, getMessage( "templateProfilePropertiesNotFound", resourceName ), null );
3561                    }
3562
3563                    url = new URL( this.getTemplateLocation(), resourceName );
3564                    in = url.openStream();
3565
3566                    if ( this.isLoggable( Level.CONFIG ) )
3567                    {
3568                        this.log( Level.CONFIG, getMessage( "templateProfilePropertiesFound", url.toExternalForm() ),
3569                                  null );
3570
3571                    }
3572
3573                    profileProperties.load( in );
3574
3575                    in.close();
3576                    in = null;
3577                }
3578                else if ( this.isLoggable( Level.CONFIG ) )
3579                {
3580                    this.log( Level.CONFIG, getMessage( "templateProfilePropertiesNotFound", resourceName ), null );
3581                }
3582            }
3583            catch ( final FileNotFoundException e )
3584            {
3585                if ( this.isLoggable( Level.CONFIG ) )
3586                {
3587                    this.log( Level.CONFIG, getMessage( "templateProfilePropertiesNotFound", url.toExternalForm() ),
3588                              null );
3589
3590                }
3591            }
3592            finally
3593            {
3594                map.put( profileName, profileProperties );
3595
3596                try
3597                {
3598                    if ( in != null )
3599                    {
3600                        in.close();
3601                    }
3602                }
3603                catch ( final IOException e )
3604                {
3605                    this.log( Level.SEVERE, getMessage( e ), e );
3606                }
3607            }
3608        }
3609
3610        return profileProperties;
3611    }
3612
3613    private Set<String> getJavaKeywords()
3614    {
3615        BufferedReader in = null;
3616        Set<String> set = this.javaKeywordsCache == null ? null : this.javaKeywordsCache.get();
3617
3618        try
3619        {
3620            if ( set == null )
3621            {
3622                in = new BufferedReader( new InputStreamReader( this.getClass().getResourceAsStream(
3623                    "/" + this.getClass().getPackage().getName().replace( ".", "/" ) + "/JavaKeywords.txt" ),
3624                                                                "UTF-8" ) );
3625
3626                final Set<String> keywords = new HashSet<String>();
3627
3628                for ( String line = in.readLine(); line != null; line = in.readLine() )
3629                {
3630                    keywords.add( line );
3631                }
3632
3633                in.close();
3634                in = null;
3635
3636                set = Collections.unmodifiableSet( keywords );
3637                this.javaKeywordsCache = new SoftReference<Set<String>>( set );
3638            }
3639        }
3640        catch ( final IOException e )
3641        {
3642            throw new IllegalStateException( getMessage( e ), e );
3643        }
3644        finally
3645        {
3646            try
3647            {
3648                if ( in != null )
3649                {
3650                    in.close();
3651                }
3652            }
3653            catch ( final IOException e )
3654            {
3655                throw new IllegalStateException( getMessage( e ), e );
3656            }
3657        }
3658
3659        return set;
3660    }
3661
3662    private Template getVelocityTemplate( final String tp, final String tn ) throws IOException
3663    {
3664        Template template = null;
3665
3666        if ( tp != null )
3667        {
3668            final String key = this.getLocale() + "|" + this.getTemplateProfile() + "|"
3669                                   + this.getDefaultTemplateProfile() + "|" + tn;
3670
3671            Map<String, TemplateData> map = this.templateCache == null
3672                                                ? null : this.templateCache.get();
3673
3674            if ( map == null )
3675            {
3676                map = new ConcurrentHashMap<String, TemplateData>( 32 );
3677                this.templateCache = new SoftReference<Map<String, TemplateData>>( map );
3678            }
3679
3680            TemplateData templateData = map.get( key );
3681
3682            if ( templateData == null )
3683            {
3684                templateData = new TemplateData();
3685
3686                if ( !StringUtils.EMPTY.equals( this.getLocale().getLanguage() ) )
3687                {
3688                    templateData.location = TEMPLATE_PREFIX + tp + "/" + this.getLocale().getLanguage() + "/" + tn;
3689                    templateData.template =
3690                        this.findVelocityTemplate( templateData.location, this.getTemplateEncoding( tp ) );
3691
3692                }
3693
3694                if ( templateData.template == null )
3695                {
3696                    templateData.location = TEMPLATE_PREFIX + tp + "/" + tn;
3697                    templateData.template =
3698                        this.findVelocityTemplate( templateData.location, this.getTemplateEncoding( tp ) );
3699
3700                }
3701
3702                if ( templateData.template == null )
3703                {
3704                    template = this.getVelocityTemplate( this.getParentTemplateProfile( tp ), tn );
3705
3706                    if ( template == null )
3707                    {
3708                        map.put( key, new TemplateData() );
3709                        throw new FileNotFoundException( getMessage( "noSuchTemplate", tn ) );
3710                    }
3711                }
3712                else
3713                {
3714                    if ( this.isLoggable( Level.FINER ) )
3715                    {
3716                        this.log( Level.FINER, getMessage( "templateInfo", tn, templateData.location ), null );
3717                    }
3718
3719                    template = templateData.template;
3720                    map.put( key, templateData );
3721                }
3722            }
3723            else if ( templateData.template == null )
3724            {
3725                throw new FileNotFoundException( getMessage( "noSuchTemplate", tn ) );
3726            }
3727            else
3728            {
3729                if ( this.isLoggable( Level.FINER ) )
3730                {
3731                    this.log( Level.FINER, getMessage( "templateInfo", tn, templateData.location ), null );
3732                }
3733
3734                template = templateData.template;
3735            }
3736        }
3737
3738        return template;
3739    }
3740
3741    private static String getMessage( final String key, final Object... arguments )
3742    {
3743        return MessageFormat.format( ResourceBundle.getBundle(
3744            JomcTool.class.getName().replace( '.', '/' ) ).getString( key ), arguments );
3745
3746    }
3747
3748    private static String getMessage( final Throwable t )
3749    {
3750        return t != null
3751                   ? t.getMessage() != null && t.getMessage().trim().length() > 0
3752                         ? t.getMessage()
3753                         : getMessage( t.getCause() )
3754                   : null;
3755
3756    }
3757
3758    /**
3759     * @since 1.3
3760     */
3761    private static class TemplateData
3762    {
3763
3764        private volatile String location;
3765
3766        private volatile Template template;
3767
3768    }
3769
3770}