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