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