View Javadoc

1   /*
2    *   Copyright (C) Christian Schulte, 2005-206
3    *   All rights reserved.
4    *
5    *   Redistribution and use in source and binary forms, with or without
6    *   modification, are permitted provided that the following conditions
7    *   are met:
8    *
9    *     o Redistributions of source code must retain the above copyright
10   *       notice, this list of conditions and the following disclaimer.
11   *
12   *     o Redistributions in binary form must reproduce the above copyright
13   *       notice, this list of conditions and the following disclaimer in
14   *       the documentation and/or other materials provided with the
15   *       distribution.
16   *
17   *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
18   *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
19   *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
20   *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
21   *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22   *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23   *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24   *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25   *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26   *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27   *
28   *   $JOMC: JomcTool.java 4365 2012-03-03 21:35:34Z schulte2005 $
29   *
30   */
31  package org.jomc.tools;
32  
33  import java.io.BufferedReader;
34  import java.io.ByteArrayInputStream;
35  import java.io.ByteArrayOutputStream;
36  import java.io.IOException;
37  import java.io.InputStream;
38  import java.io.InputStreamReader;
39  import java.io.OutputStreamWriter;
40  import java.io.Reader;
41  import java.io.StringReader;
42  import java.lang.ref.Reference;
43  import java.lang.ref.SoftReference;
44  import java.lang.reflect.InvocationTargetException;
45  import java.net.URL;
46  import java.text.DateFormat;
47  import java.text.Format;
48  import java.text.MessageFormat;
49  import java.text.SimpleDateFormat;
50  import java.util.ArrayList;
51  import java.util.Calendar;
52  import java.util.Collections;
53  import java.util.Enumeration;
54  import java.util.HashMap;
55  import java.util.HashSet;
56  import java.util.LinkedList;
57  import java.util.List;
58  import java.util.Locale;
59  import java.util.Map;
60  import java.util.ResourceBundle;
61  import java.util.Set;
62  import java.util.logging.Level;
63  import javax.activation.MimeType;
64  import javax.activation.MimeTypeParseException;
65  import org.apache.commons.io.IOUtils;
66  import org.apache.commons.lang.StringEscapeUtils;
67  import org.apache.commons.lang.StringUtils;
68  import org.apache.velocity.Template;
69  import org.apache.velocity.VelocityContext;
70  import org.apache.velocity.app.VelocityEngine;
71  import org.apache.velocity.exception.ParseErrorException;
72  import org.apache.velocity.exception.ResourceNotFoundException;
73  import org.apache.velocity.exception.VelocityException;
74  import org.apache.velocity.runtime.RuntimeConstants;
75  import org.apache.velocity.runtime.RuntimeServices;
76  import org.apache.velocity.runtime.log.LogChute;
77  import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
78  import org.apache.velocity.runtime.resource.loader.URLResourceLoader;
79  import org.jomc.model.Argument;
80  import org.jomc.model.ArgumentType;
81  import org.jomc.model.Dependency;
82  import org.jomc.model.Implementation;
83  import org.jomc.model.InheritanceModel;
84  import org.jomc.model.Message;
85  import org.jomc.model.ModelObject;
86  import org.jomc.model.Modules;
87  import org.jomc.model.Multiplicity;
88  import org.jomc.model.Properties;
89  import org.jomc.model.Property;
90  import org.jomc.model.Specification;
91  import org.jomc.model.SpecificationReference;
92  import org.jomc.model.Specifications;
93  import org.jomc.model.Text;
94  import org.jomc.model.Texts;
95  import org.jomc.model.modlet.ModelHelper;
96  import org.jomc.modlet.Model;
97  
98  /**
99   * Base tool class.
100  *
101  * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a>
102  * @version $JOMC: JomcTool.java 4365 2012-03-03 21:35:34Z schulte2005 $
103  */
104 public class JomcTool
105 {
106 
107     /** Listener interface. */
108     public abstract static class Listener
109     {
110 
111         /** Creates a new {@code Listener} instance. */
112         public Listener()
113         {
114             super();
115         }
116 
117         /**
118          * Gets called on logging.
119          *
120          * @param level The level of the event.
121          * @param message The message of the event or {@code null}.
122          * @param throwable The throwable of the event or {@code null}.
123          *
124          * @throws NullPointerException if {@code level} is {@code null}.
125          */
126         public void onLog( final Level level, final String message, final Throwable throwable )
127         {
128             if ( level == null )
129             {
130                 throw new NullPointerException( "level" );
131             }
132         }
133 
134     }
135 
136     /** Empty byte array. */
137     private static final byte[] NO_BYTES =
138     {
139     };
140 
141     /** The prefix of the template location. */
142     private static final String TEMPLATE_PREFIX =
143         JomcTool.class.getPackage().getName().replace( '.', '/' ) + "/templates/";
144 
145     /** Constant for the default template profile. */
146     private static final String DEFAULT_TEMPLATE_PROFILE = "jomc-java";
147 
148     /** The default template profile. */
149     private static volatile String defaultTemplateProfile;
150 
151     /**
152      * The log level events are logged at by default.
153      * @see #getDefaultLogLevel()
154      */
155     private static final Level DEFAULT_LOG_LEVEL = Level.WARNING;
156 
157     /** The default log level. */
158     private static volatile Level defaultLogLevel;
159 
160     /** The model of the instance. */
161     private Model model;
162 
163     /** The {@code VelocityEngine} of the instance. */
164     private VelocityEngine velocityEngine;
165 
166     /** The encoding to use for reading templates. */
167     private String templateEncoding;
168 
169     /**
170      * The location to search for templates in addition to searching the class path.
171      * @since 1.2
172      */
173     private URL templateLocation;
174 
175     /** The encoding to use for reading files. */
176     private String inputEncoding;
177 
178     /** The encoding to use for writing files. */
179     private String outputEncoding;
180 
181     /**
182      * The template parameters.
183      * @since 1.2
184      */
185     private Map<String, Object> templateParameters;
186 
187     /** The template profile of the instance. */
188     private String templateProfile;
189 
190     /** The indentation string of the instance. */
191     private String indentation;
192 
193     /** The line separator of the instance. */
194     private String lineSeparator;
195 
196     /** The listeners of the instance. */
197     private List<Listener> listeners;
198 
199     /** The log level of the instance. */
200     private Level logLevel;
201 
202     /**
203      * The locale of the instance.
204      * @since 1.2
205      */
206     private Locale locale;
207 
208     /** Cached indentation strings. */
209     private volatile Reference<Map<String, String>> indentationCache;
210 
211     /** Cached template locations. */
212     private volatile Reference<Map<String, String>> templateLocationsCache;
213 
214     /** Cached template profile properties. */
215     private volatile Reference<Map<String, java.util.Properties>> templateProfilePropertiesCache;
216 
217     /** Cached Java keywords. */
218     private volatile Reference<Set<String>> javaKeywordsCache;
219 
220     /** Creates a new {@code JomcTool} instance. */
221     public JomcTool()
222     {
223         super();
224     }
225 
226     /**
227      * Creates a new {@code JomcTool} instance taking a {@code JomcTool} instance to initialize the new instance with.
228      *
229      * @param tool The instance to initialize the new instance with.
230      *
231      * @throws NullPointerException if {@code tool} is {@code null}.
232      * @throws IOException if copying {@code tool} fails.
233      */
234     public JomcTool( final JomcTool tool ) throws IOException
235     {
236         this();
237 
238         if ( tool == null )
239         {
240             throw new NullPointerException( "tool" );
241         }
242 
243         this.indentation = tool.indentation;
244         this.inputEncoding = tool.inputEncoding;
245         this.lineSeparator = tool.lineSeparator;
246         this.listeners = tool.listeners != null ? new LinkedList<Listener>( tool.listeners ) : null;
247         this.logLevel = tool.logLevel;
248         this.model = tool.model != null ? tool.model.clone() : null;
249         this.outputEncoding = tool.outputEncoding;
250         this.templateEncoding = tool.templateEncoding;
251         this.templateProfile = tool.templateProfile;
252         this.velocityEngine = tool.velocityEngine;
253         this.locale = tool.locale;
254         this.templateParameters =
255             tool.templateParameters != null ? new HashMap<String, Object>( tool.templateParameters ) : null;
256 
257         this.templateLocation =
258             tool.templateLocation != null ? new URL( tool.templateLocation.toExternalForm() ) : null;
259 
260     }
261 
262     /**
263      * Gets the list of registered listeners.
264      * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
265      * to the returned list will be present inside the object. This is why there is no {@code set} method for the
266      * listeners property.</p>
267      *
268      * @return The list of registered listeners.
269      *
270      * @see #log(java.util.logging.Level, java.lang.String, java.lang.Throwable)
271      */
272     public List<Listener> getListeners()
273     {
274         if ( this.listeners == null )
275         {
276             this.listeners = new LinkedList<Listener>();
277         }
278 
279         return this.listeners;
280     }
281 
282     /**
283      * Gets the default log level events are logged at.
284      * <p>The default log level is controlled by system property {@code org.jomc.tools.JomcTool.defaultLogLevel} holding
285      * the log level to log events at by default. If that property is not set, the {@code WARNING} default is
286      * returned.</p>
287      *
288      * @return The log level events are logged at by default.
289      *
290      * @see #getLogLevel()
291      * @see Level#parse(java.lang.String)
292      */
293     public static Level getDefaultLogLevel()
294     {
295         if ( defaultLogLevel == null )
296         {
297             defaultLogLevel = Level.parse( System.getProperty( "org.jomc.tools.JomcTool.defaultLogLevel",
298                                                                DEFAULT_LOG_LEVEL.getName() ) );
299 
300         }
301 
302         return defaultLogLevel;
303     }
304 
305     /**
306      * Sets the default log level events are logged at.
307      *
308      * @param value The new default level events are logged at or {@code null}.
309      *
310      * @see #getDefaultLogLevel()
311      */
312     public static void setDefaultLogLevel( final Level value )
313     {
314         defaultLogLevel = value;
315     }
316 
317     /**
318      * Gets the log level of the instance.
319      *
320      * @return The log level of the instance.
321      *
322      * @see #getDefaultLogLevel()
323      * @see #setLogLevel(java.util.logging.Level)
324      * @see #isLoggable(java.util.logging.Level)
325      */
326     public final Level getLogLevel()
327     {
328         if ( this.logLevel == null )
329         {
330             this.logLevel = getDefaultLogLevel();
331 
332             if ( this.isLoggable( Level.CONFIG ) )
333             {
334                 this.log( Level.CONFIG, getMessage( "defaultLogLevelInfo", this.logLevel.getLocalizedName() ), null );
335             }
336         }
337 
338         return this.logLevel;
339     }
340 
341     /**
342      * Sets the log level of the instance.
343      *
344      * @param value The new log level of the instance or {@code null}.
345      *
346      * @see #getLogLevel()
347      * @see #isLoggable(java.util.logging.Level)
348      */
349     public final void setLogLevel( final Level value )
350     {
351         this.logLevel = value;
352     }
353 
354     /**
355      * Checks if a message at a given level is provided to the listeners of the instance.
356      *
357      * @param level The level to test.
358      *
359      * @return {@code true}, if messages at {@code level} are provided to the listeners of the instance;
360      * {@code false}, if messages at {@code level} are not provided to the listeners of the instance.
361      *
362      * @throws NullPointerException if {@code level} is {@code null}.
363      *
364      * @see #getLogLevel()
365      * @see #setLogLevel(java.util.logging.Level)
366      * @see #log(java.util.logging.Level, java.lang.String, java.lang.Throwable)
367      */
368     public boolean isLoggable( final Level level )
369     {
370         if ( level == null )
371         {
372             throw new NullPointerException( "level" );
373         }
374 
375         return level.intValue() >= this.getLogLevel().intValue();
376     }
377 
378     /**
379      * Gets the Java package name of a specification.
380      *
381      * @param specification The specification to get the Java package name of.
382      *
383      * @return The Java package name of {@code specification} or {@code null}.
384      *
385      * @throws NullPointerException if {@code specification} is {@code null}.
386      */
387     public String getJavaPackageName( final Specification specification )
388     {
389         if ( specification == null )
390         {
391             throw new NullPointerException( "specification" );
392         }
393 
394         return specification.getClazz() != null ? this.getJavaPackageName( specification.getClazz() ) : null;
395     }
396 
397     /**
398      * Gets the Java type name of a specification.
399      *
400      * @param specification The specification to get the Java type name of.
401      * @param qualified {@code true}, to return the fully qualified type name (with package name prepended);
402      * {@code false}, to return the short type name (without package name prepended).
403      *
404      * @return The Java type name of {@code specification} or {@code null}.
405      *
406      * @throws NullPointerException if {@code specification} is {@code null}.
407      */
408     public String getJavaTypeName( final Specification specification, final boolean qualified )
409     {
410         if ( specification == null )
411         {
412             throw new NullPointerException( "specification" );
413         }
414 
415         if ( specification.getClazz() != null )
416         {
417             final StringBuilder typeName = new StringBuilder( specification.getClazz().length() );
418             final String javaPackageName = this.getJavaPackageName( specification );
419 
420             if ( qualified && javaPackageName.length() > 0 )
421             {
422                 typeName.append( javaPackageName ).append( '.' );
423             }
424 
425             typeName.append( javaPackageName.length() > 0
426                              ? specification.getClazz().substring( javaPackageName.length() + 1 )
427                              : specification.getClazz() );
428 
429             return typeName.toString();
430         }
431 
432         return null;
433     }
434 
435     /**
436      * Gets the Java class path location of a specification.
437      *
438      * @param specification The specification to return the Java class path location of.
439      *
440      * @return The Java class path location of {@code specification} or {@code null}.
441      *
442      * @throws NullPointerException if {@code specification} is {@code null}.
443      */
444     public String getJavaClasspathLocation( final Specification specification )
445     {
446         if ( specification == null )
447         {
448             throw new NullPointerException( "specification" );
449         }
450 
451         return specification.getClazz() != null
452                ? ( this.getJavaTypeName( specification, true ) ).replace( '.', '/' )
453                : null;
454 
455     }
456 
457     /**
458      * Gets the Java package name of a specification reference.
459      *
460      * @param reference The specification reference to get the Java package name of.
461      *
462      * @return The Java package name of {@code reference} or {@code null}.
463      *
464      * @throws NullPointerException if {@code reference} is {@code null}.
465      */
466     public String getJavaPackageName( final SpecificationReference reference )
467     {
468         if ( reference == null )
469         {
470             throw new NullPointerException( "reference" );
471         }
472 
473         final Specification s = this.getModules().getSpecification( reference.getIdentifier() );
474         assert s != null : "Specification '" + reference.getIdentifier() + "' not found.";
475         return s.getClazz() != null ? this.getJavaPackageName( s ) : null;
476     }
477 
478     /**
479      * Gets the name of a Java type of a given specification reference.
480      *
481      * @param reference The specification reference to get a Java type name of.
482      * @param qualified {@code true}, to return the fully qualified type name (with package name prepended);
483      * {@code false}, to return the short type name (without package name prepended).
484      *
485      * @return The Java type name of {@code reference} or {@code null}.
486      *
487      * @throws NullPointerException if {@code reference} is {@code null}.
488      */
489     public String getJavaTypeName( final SpecificationReference reference, final boolean qualified )
490     {
491         if ( reference == null )
492         {
493             throw new NullPointerException( "reference" );
494         }
495 
496         final Specification s = this.getModules().getSpecification( reference.getIdentifier() );
497         assert s != null : "Specification '" + reference.getIdentifier() + "' not found.";
498         return s.getClazz() != null ? this.getJavaTypeName( s, qualified ) : null;
499     }
500 
501     /**
502      * Gets the Java package name of an implementation.
503      *
504      * @param implementation The implementation to get the Java package name of.
505      *
506      * @return The Java package name of {@code implementation} or {@code null}.
507      *
508      * @throws NullPointerException if {@code implementation} is {@code null}.
509      */
510     public String getJavaPackageName( final Implementation implementation )
511     {
512         if ( implementation == null )
513         {
514             throw new NullPointerException( "implementation" );
515         }
516 
517         return implementation.getClazz() != null ? this.getJavaPackageName( implementation.getClazz() ) : null;
518     }
519 
520     /**
521      * Gets the Java type name of an implementation.
522      *
523      * @param implementation The implementation to get the Java type name of.
524      * @param qualified {@code true}, to return the fully qualified type name (with package name prepended);
525      * {@code false}, to return the short type name (without package name prepended).
526      *
527      * @return The Java type name of {@code implementation} or {@code null}.
528      *
529      * @throws NullPointerException if {@code implementation} is {@code null}.
530      */
531     public String getJavaTypeName( final Implementation implementation, final boolean qualified )
532     {
533         if ( implementation == null )
534         {
535             throw new NullPointerException( "implementation" );
536         }
537 
538         if ( implementation.getClazz() != null )
539         {
540             final StringBuilder typeName = new StringBuilder( implementation.getClazz().length() );
541             final String javaPackageName = this.getJavaPackageName( implementation );
542 
543             if ( qualified && javaPackageName.length() > 0 )
544             {
545                 typeName.append( javaPackageName ).append( '.' );
546             }
547 
548             typeName.append( javaPackageName.length() > 0
549                              ? implementation.getClazz().substring( javaPackageName.length() + 1 )
550                              : implementation.getClazz() );
551 
552             return typeName.toString();
553         }
554 
555         return null;
556     }
557 
558     /**
559      * Gets the Java class path location of an implementation.
560      *
561      * @param implementation The implementation to return the Java class path location of.
562      *
563      * @return The Java class path location of {@code implementation} or {@code null}.
564      *
565      * @throws NullPointerException if {@code implementation} is {@code null}.
566      */
567     public String getJavaClasspathLocation( final Implementation implementation )
568     {
569         if ( implementation == null )
570         {
571             throw new NullPointerException( "implementation" );
572         }
573 
574         return implementation.getClazz() != null
575                ? ( this.getJavaTypeName( implementation, true ) ).replace( '.', '/' )
576                : null;
577 
578     }
579 
580     /**
581      * Gets a list of names of all Java types an implementation implements.
582      *
583      * @param implementation The implementation to get names of all implemented Java types of.
584      * @param qualified {@code true}, to return the fully qualified type names (with package name prepended);
585      * {@code false}, to return the short type names (without package name prepended).
586      *
587      * @return An unmodifiable list of names of all Java types implemented by {@code implementation}.
588      *
589      * @throws NullPointerException if {@code implementation} is {@code null}.
590      *
591      * @deprecated As of JOMC 1.2, replaced by method {@link #getImplementedJavaTypeNames(org.jomc.model.Implementation, boolean)}.
592      * This method will be removed in version 2.0.
593      */
594     @Deprecated
595     public List<String> getJavaInterfaceNames( final Implementation implementation, final boolean qualified )
596     {
597         if ( implementation == null )
598         {
599             throw new NullPointerException( "implementation" );
600         }
601 
602         return this.getImplementedJavaTypeNames( implementation, qualified );
603     }
604 
605     /**
606      * Gets a list of names of all Java types an implementation implements.
607      *
608      * @param implementation The implementation to get names of all implemented Java types of.
609      * @param qualified {@code true}, to return the fully qualified type names (with package name prepended);
610      * {@code false}, to return the short type names (without package name prepended).
611      *
612      * @return An unmodifiable list of names of all Java types implemented by {@code implementation}.
613      *
614      * @throws NullPointerException if {@code implementation} is {@code null}.
615      *
616      * @since 1.2
617      */
618     public List<String> getImplementedJavaTypeNames( final Implementation implementation, final boolean qualified )
619     {
620         if ( implementation == null )
621         {
622             throw new NullPointerException( "implementation" );
623         }
624 
625         final Specifications specs = this.getModules().getSpecifications( implementation.getIdentifier() );
626         final List<String> col = new ArrayList<String>( specs == null ? 0 : specs.getSpecification().size() );
627 
628         if ( specs != null )
629         {
630             for ( int i = 0, s0 = specs.getSpecification().size(); i < s0; i++ )
631             {
632                 final Specification s = specs.getSpecification().get( i );
633 
634                 if ( s.getClazz() != null )
635                 {
636                     final String typeName = this.getJavaTypeName( s, qualified );
637                     if ( !col.contains( typeName ) )
638                     {
639                         col.add( typeName );
640                     }
641                 }
642             }
643         }
644 
645         return Collections.unmodifiableList( col );
646     }
647 
648     /**
649      * Gets the Java type name of an argument.
650      *
651      * @param argument The argument to get the Java type name of.
652      *
653      * @return The Java type name of {@code argument}.
654      *
655      * @throws NullPointerException if {@code argument} is {@code null}.
656      */
657     public String getJavaTypeName( final Argument argument )
658     {
659         if ( argument == null )
660         {
661             throw new NullPointerException( "argument" );
662         }
663 
664         String javaTypeName = "java.lang.String";
665 
666         if ( argument.getType() == ArgumentType.DATE || argument.getType() == ArgumentType.TIME )
667         {
668             javaTypeName = "java.util.Date";
669         }
670         else if ( argument.getType() == ArgumentType.NUMBER )
671         {
672             javaTypeName = "java.lang.Number";
673         }
674 
675         return javaTypeName;
676     }
677 
678     /**
679      * Gets a Java method parameter name of an argument.
680      *
681      * @param argument The argument to get the Java method parameter name of.
682      *
683      * @return The Java method parameter name of {@code argument}.
684      *
685      * @throws NullPointerException if {@code argument} is {@code null}.
686      *
687      * @since 1.2
688      */
689     public String getJavaMethodParameterName( final Argument argument )
690     {
691         if ( argument == null )
692         {
693             throw new NullPointerException( "argument" );
694         }
695 
696         return this.getJavaMethodParameterName( argument.getName() );
697     }
698 
699     /**
700      * Gets the Java type name of a property.
701      *
702      * @param property The property to get the Java type name of.
703      * @param boxify {@code true}, to return the name of the Java wrapper class when the type is a Java primitive type;
704      * {@code false}, to return the exact binary name (unboxed name) of the Java type.
705      *
706      * @return The Java type name of {@code property}.
707      *
708      * @throws NullPointerException if {@code property} is {@code null}.
709      */
710     public String getJavaTypeName( final Property property, final boolean boxify )
711     {
712         if ( property == null )
713         {
714             throw new NullPointerException( "property" );
715         }
716 
717         if ( property.getType() != null )
718         {
719             final String typeName = property.getType();
720 
721             if ( boxify )
722             {
723                 if ( Boolean.TYPE.getName().equals( typeName ) )
724                 {
725                     return Boolean.class.getName();
726                 }
727                 if ( Byte.TYPE.getName().equals( typeName ) )
728                 {
729                     return Byte.class.getName();
730                 }
731                 if ( Character.TYPE.getName().equals( typeName ) )
732                 {
733                     return Character.class.getName();
734                 }
735                 if ( Double.TYPE.getName().equals( typeName ) )
736                 {
737                     return Double.class.getName();
738                 }
739                 if ( Float.TYPE.getName().equals( typeName ) )
740                 {
741                     return Float.class.getName();
742                 }
743                 if ( Integer.TYPE.getName().equals( typeName ) )
744                 {
745                     return Integer.class.getName();
746                 }
747                 if ( Long.TYPE.getName().equals( typeName ) )
748                 {
749                     return Long.class.getName();
750                 }
751                 if ( Short.TYPE.getName().equals( typeName ) )
752                 {
753                     return Short.class.getName();
754                 }
755             }
756 
757             return typeName;
758         }
759 
760         return property.getAny() != null ? Object.class.getName() : String.class.getName();
761     }
762 
763     /**
764      * Gets a flag indicating the type of a given property is a Java primitive.
765      *
766      * @param property The property to query.
767      *
768      * @return {@code true}, if the Java type of {@code property} is primitive; {@code false}, if not.
769      *
770      * @throws NullPointerException if {@code property} is {@code null}.
771      */
772     public boolean isJavaPrimitiveType( final Property property )
773     {
774         if ( property == null )
775         {
776             throw new NullPointerException( "property" );
777         }
778 
779         return !this.getJavaTypeName( property, false ).equals( this.getJavaTypeName( property, true ) );
780     }
781 
782     /**
783      * Gets the name of a Java getter method of a given property.
784      *
785      * @param property The property to get a Java getter method name of.
786      *
787      * @return The Java getter method name of {@code property}.
788      *
789      * @throws NullPointerException if {@code property} is {@code null}.
790      *
791      * @see #getJavaIdentifier(java.lang.String, boolean)
792      */
793     public String getJavaGetterMethodName( final Property property )
794     {
795         if ( property == null )
796         {
797             throw new NullPointerException( "property" );
798         }
799 
800         String prefix = "get";
801 
802         final String javaTypeName = this.getJavaTypeName( property, true );
803         if ( Boolean.class.getName().equals( javaTypeName ) )
804         {
805             prefix = "is";
806         }
807 
808         return prefix + this.getJavaIdentifier( property.getName(), true );
809     }
810 
811     /**
812      * Gets the name of a Java setter method of a given property.
813      *
814      * @param property The property to get a Java setter method name of.
815      *
816      * @return The Java setter method name of {@code property}.
817      *
818      * @throws NullPointerException if {@code property} is {@code null}.
819      *
820      * @see #getJavaIdentifier(java.lang.String, boolean)
821      *
822      * @since 1.2
823      */
824     public String getJavaSetterMethodName( final Property property )
825     {
826         if ( property == null )
827         {
828             throw new NullPointerException( "property" );
829         }
830 
831         return "set" + this.getJavaIdentifier( property.getName(), true );
832     }
833 
834     /**
835      * Gets a Java method parameter name of a property.
836      *
837      * @param property The property to get the Java method parameter name of.
838      *
839      * @return The Java method parameter name of {@code property}.
840      *
841      * @throws NullPointerException if {@code property} is {@code null}.
842      *
843      * @since 1.2
844      */
845     public String getJavaMethodParameterName( final Property property )
846     {
847         if ( property == null )
848         {
849             throw new NullPointerException( "property" );
850         }
851 
852         return this.getJavaMethodParameterName( property.getName() );
853     }
854 
855     /**
856      * Gets the name of a Java type of a given dependency.
857      *
858      * @param dependency The dependency to get a dependency Java type name of.
859      *
860      * @return The Java type name of {@code dependency} or {@code null}.
861      *
862      * @throws NullPointerException if {@code dependency} is {@code null}.
863      */
864     public String getJavaTypeName( final Dependency dependency )
865     {
866         if ( dependency == null )
867         {
868             throw new NullPointerException( "dependency" );
869         }
870 
871         final Specification s = this.getModules().getSpecification( dependency.getIdentifier() );
872 
873         if ( s != null && s.getClazz() != null )
874         {
875             final StringBuilder typeName = new StringBuilder( s.getClazz().length() );
876             typeName.append( this.getJavaTypeName( s, true ) );
877             if ( s.getMultiplicity() == Multiplicity.MANY && dependency.getImplementationName() == null )
878             {
879                 typeName.append( "[]" );
880             }
881 
882             return typeName.toString();
883         }
884 
885         return null;
886     }
887 
888     /**
889      * Gets the name of a Java getter method of a given dependency.
890      *
891      * @param dependency The dependency to get a Java getter method name of.
892      *
893      * @return The Java getter method name of {@code dependency}.
894      *
895      * @throws NullPointerException if {@code dependency} is {@code null}.
896      *
897      * @see #getJavaIdentifier(java.lang.String, boolean)
898      */
899     public String getJavaGetterMethodName( final Dependency dependency )
900     {
901         if ( dependency == null )
902         {
903             throw new NullPointerException( "dependency" );
904         }
905 
906         return "get" + this.getJavaIdentifier( dependency.getName(), true );
907     }
908 
909     /**
910      * Gets the name of a Java setter method of a given dependency.
911      *
912      * @param dependency The dependency to get a Java setter method name of.
913      *
914      * @return The Java setter method name of {@code dependency}.
915      *
916      * @throws NullPointerException if {@code dependency} is {@code null}.
917      *
918      * @see #getJavaIdentifier(java.lang.String, boolean)
919      *
920      * @since 1.2
921      */
922     public String getJavaSetterMethodName( final Dependency dependency )
923     {
924         if ( dependency == null )
925         {
926             throw new NullPointerException( "dependency" );
927         }
928 
929         return "set" + this.getJavaIdentifier( dependency.getName(), true );
930     }
931 
932     /**
933      * Gets a Java method parameter name of a dependency.
934      *
935      * @param dependency The dependency to get the Java method parameter name of.
936      *
937      * @return The Java method parameter name of {@code dependency}.
938      *
939      * @throws NullPointerException if {@code dependency} is {@code null}.
940      *
941      * @since 1.2
942      */
943     public String getJavaMethodParameterName( final Dependency dependency )
944     {
945         if ( dependency == null )
946         {
947             throw new NullPointerException( "dependency" );
948         }
949 
950         return this.getJavaMethodParameterName( dependency.getName() );
951     }
952 
953     /**
954      * Gets the name of a Java getter method of a given message.
955      *
956      * @param message The message to get a Java getter method name of.
957      *
958      * @return The Java getter method name of {@code message}.
959      *
960      * @throws NullPointerException if {@code message} is {@code null}.
961      *
962      * @see #getJavaIdentifier(java.lang.String, boolean)
963      */
964     public String getJavaGetterMethodName( final Message message )
965     {
966         if ( message == null )
967         {
968             throw new NullPointerException( "message" );
969         }
970 
971         return "get" + this.getJavaIdentifier( message.getName(), true );
972     }
973 
974     /**
975      * Gets the name of a Java setter method of a given message.
976      *
977      * @param message The message to get a Java setter method name of.
978      *
979      * @return The Java setter method name of {@code message}.
980      *
981      * @throws NullPointerException if {@code message} is {@code null}.
982      *
983      * @see #getJavaIdentifier(java.lang.String, boolean)
984      *
985      * @since 1.2
986      */
987     public String getJavaSetterMethodName( final Message message )
988     {
989         if ( message == null )
990         {
991             throw new NullPointerException( "message" );
992         }
993 
994         return "set" + this.getJavaIdentifier( message.getName(), true );
995     }
996 
997     /**
998      * Gets a Java method parameter name of a message.
999      *
1000      * @param message The message to get the Java method parameter name of.
1001      *
1002      * @return The Java method parameter name of {@code message}.
1003      *
1004      * @throws NullPointerException if {@code message} is {@code null}.
1005      *
1006      * @since 1.2
1007      */
1008     public String getJavaMethodParameterName( final Message message )
1009     {
1010         if ( message == null )
1011         {
1012             throw new NullPointerException( "message" );
1013         }
1014 
1015         return this.getJavaMethodParameterName( message.getName() );
1016     }
1017 
1018     /**
1019      * Gets the Java modifier name of a dependency of a given implementation.
1020      *
1021      * @param implementation The implementation declaring the dependency to get a Java modifier name of.
1022      * @param dependency The dependency to get a Java modifier name of.
1023      *
1024      * @return The Java modifier name of {@code dependency} of {@code implementation}.
1025      *
1026      * @throws NullPointerException if {@code implementation} or {@code dependency} is {@code null}.
1027      */
1028     public String getJavaModifierName( final Implementation implementation, final Dependency dependency )
1029     {
1030         if ( implementation == null )
1031         {
1032             throw new NullPointerException( "implementation" );
1033         }
1034         if ( dependency == null )
1035         {
1036             throw new NullPointerException( "dependency" );
1037         }
1038 
1039         return "private";
1040     }
1041 
1042     /**
1043      * Gets the Java modifier name of a message of a given implementation.
1044      *
1045      * @param implementation The implementation declaring the message to get a Java modifier name of.
1046      * @param message The message to get a Java modifier name of.
1047      *
1048      * @return The Java modifier name of {@code message} of {@code implementation}.
1049      *
1050      * @throws NullPointerException if {@code implementation} or {@code message} is {@code null}.
1051      */
1052     public String getJavaModifierName( final Implementation implementation, final Message message )
1053     {
1054         if ( implementation == null )
1055         {
1056             throw new NullPointerException( "implementation" );
1057         }
1058         if ( message == null )
1059         {
1060             throw new NullPointerException( "message" );
1061         }
1062 
1063         return "private";
1064     }
1065 
1066     /**
1067      * Gets the Java modifier name of a property of a given implementation.
1068      *
1069      * @param implementation The implementation declaring the property to get a Java modifier name of.
1070      * @param property The property to get a Java modifier name of.
1071      *
1072      * @return The Java modifier name of {@code property} of {@code implementation}.
1073      *
1074      * @throws NullPointerException if {@code implementation} or {@code property} is {@code null}.
1075      */
1076     public String getJavaModifierName( final Implementation implementation, final Property property )
1077     {
1078         if ( implementation == null )
1079         {
1080             throw new NullPointerException( "implementation" );
1081         }
1082         if ( property == null )
1083         {
1084             throw new NullPointerException( "property" );
1085         }
1086 
1087         String modifier = "private";
1088         final Properties specified = this.getModules().getSpecifiedProperties( implementation.getIdentifier() );
1089 
1090         if ( specified != null && specified.getProperty( property.getName() ) != null )
1091         {
1092             modifier = "public";
1093         }
1094 
1095         return modifier;
1096     }
1097 
1098     /**
1099      * Formats a text to a Javadoc comment.
1100      *
1101      * @param text The text to format to a Javadoc comment.
1102      * @param indentationLevel The indentation level of the comment.
1103      * @param linePrefix The text to prepend lines with.
1104      *
1105      * @return {@code text} formatted to a Javadoc comment.
1106      *
1107      * @throws NullPointerException if {@code text} or {@code linePrefix} is {@code null}.
1108      * @throws IllegalArgumentException if {@code indentationLevel} is negative.
1109      */
1110     public String getJavadocComment( final Text text, final int indentationLevel, final String linePrefix )
1111     {
1112         if ( text == null )
1113         {
1114             throw new NullPointerException( "text" );
1115         }
1116         if ( linePrefix == null )
1117         {
1118             throw new NullPointerException( "linePrefix" );
1119         }
1120         if ( indentationLevel < 0 )
1121         {
1122             throw new IllegalArgumentException( Integer.toString( indentationLevel ) );
1123         }
1124 
1125         BufferedReader reader = null;
1126         boolean suppressExceptionOnClose = true;
1127 
1128         try
1129         {
1130             String javadoc = "";
1131 
1132             if ( text.getValue() != null )
1133             {
1134                 final String indent = this.getIndentation( indentationLevel );
1135                 reader = new BufferedReader( new StringReader( text.getValue() ) );
1136                 final StringBuilder builder = new StringBuilder( text.getValue().length() );
1137 
1138                 String line;
1139                 while ( ( line = reader.readLine() ) != null )
1140                 {
1141                     builder.append( this.getLineSeparator() ).append( indent ).append( linePrefix ).
1142                         append( line.replaceAll( "\\/\\*\\*", "/*" ).replaceAll( "\\*/", "/" ) );
1143 
1144                 }
1145 
1146                 if ( builder.length() > 0 )
1147                 {
1148                     javadoc =
1149                         builder.substring( this.getLineSeparator().length() + indent.length() + linePrefix.length() );
1150 
1151                     if ( !new MimeType( text.getType() ).match( "text/html" ) )
1152                     {
1153                         javadoc = StringEscapeUtils.escapeHtml( javadoc );
1154                     }
1155                 }
1156             }
1157 
1158             suppressExceptionOnClose = false;
1159             return javadoc;
1160         }
1161         catch ( final MimeTypeParseException e )
1162         {
1163             throw new AssertionError( e );
1164         }
1165         catch ( final IOException e )
1166         {
1167             throw new AssertionError( e );
1168         }
1169         finally
1170         {
1171             try
1172             {
1173                 if ( reader != null )
1174                 {
1175                     reader.close();
1176                 }
1177             }
1178             catch ( final IOException e )
1179             {
1180                 if ( suppressExceptionOnClose )
1181                 {
1182                     this.log( Level.SEVERE, getMessage( e ), e );
1183                 }
1184                 else
1185                 {
1186                     throw new AssertionError( e );
1187                 }
1188             }
1189         }
1190     }
1191 
1192     /**
1193      * Formats a text from a list of texts to a Javadoc comment.
1194      *
1195      * @param texts The list of texts to format to a Javadoc comment.
1196      * @param indentationLevel The indentation level of the comment.
1197      * @param linePrefix The text to prepend lines with.
1198      *
1199      * @return The text corresponding to the locale of the instance from the list of texts formatted to a Javadoc
1200      * comment.
1201      *
1202      * @throws NullPointerException if {@code texts} or {@code linePrefix} is {@code null}.
1203      * @throws IllegalArgumentException if {@code indentationLevel} is negative.
1204      *
1205      * @see #getLocale()
1206      *
1207      * @since 1.2
1208      */
1209     public String getJavadocComment( final Texts texts, final int indentationLevel, final String linePrefix )
1210     {
1211         if ( texts == null )
1212         {
1213             throw new NullPointerException( "texts" );
1214         }
1215         if ( linePrefix == null )
1216         {
1217             throw new NullPointerException( "linePrefix" );
1218         }
1219         if ( indentationLevel < 0 )
1220         {
1221             throw new IllegalArgumentException( Integer.toString( indentationLevel ) );
1222         }
1223 
1224         return this.getJavadocComment( texts.getText( this.getLocale().getLanguage() ), indentationLevel, linePrefix );
1225     }
1226 
1227     /**
1228      * Formats a string to a Java string with unicode escapes.
1229      *
1230      * @param str The string to format to a Java string or {@code null}.
1231      *
1232      * @return {@code str} formatted to a Java string or {@code null}.
1233      *
1234      * @see StringEscapeUtils#escapeJava(java.lang.String)
1235      */
1236     public String getJavaString( final String str )
1237     {
1238         return StringEscapeUtils.escapeJava( str );
1239     }
1240 
1241     /**
1242      * Formats a string to a Java identifier.
1243      *
1244      * @param str The string to format or {@code null}.
1245      * @param capitalize {@code true}, to return an identifier with the first character upper cased; {@code false}, to
1246      * return an identifier with the first character lower cased.
1247      *
1248      * @return {@code str} formatted to a Java identifier or {@code null}.
1249      *
1250      * @since 1.2
1251      */
1252     public String getJavaIdentifier( final String str, final boolean capitalize )
1253     {
1254         String identifier = null;
1255 
1256         if ( str != null )
1257         {
1258             final int len = str.length();
1259             final StringBuilder builder = new StringBuilder( len );
1260             boolean uc = capitalize;
1261 
1262             for ( int i = 0; i < len; i++ )
1263             {
1264                 final char c = str.charAt( i );
1265 
1266                 if ( builder.length() > 0 )
1267                 {
1268                     if ( Character.isJavaIdentifierPart( c ) )
1269                     {
1270                         builder.append( uc ? Character.toUpperCase( c ) : c );
1271                         uc = false;
1272                     }
1273                     else
1274                     {
1275                         uc = true;
1276                     }
1277                 }
1278                 else
1279                 {
1280                     if ( Character.isJavaIdentifierStart( c ) )
1281                     {
1282                         builder.append( uc ? Character.toUpperCase( c ) : Character.toLowerCase( c ) );
1283                         uc = false;
1284                     }
1285                     else
1286                     {
1287                         uc = capitalize;
1288                     }
1289                 }
1290             }
1291 
1292             identifier = builder.toString();
1293 
1294             if ( identifier.length() <= 0 && this.isLoggable( Level.WARNING ) )
1295             {
1296                 this.log( Level.WARNING, getMessage( "invalidJavaIdentifier", str ), null );
1297             }
1298         }
1299 
1300         return identifier;
1301     }
1302 
1303     /**
1304      * Formats a string to a Java method parameter name.
1305      *
1306      * @param str The string to format or {@code null}.
1307      *
1308      * @return {@code str} formatted to a Java method parameter name or {@code null}.
1309      *
1310      * @since 1.3
1311      */
1312     private String getJavaMethodParameterName( final String str )
1313     {
1314         String methodParameterName = null;
1315 
1316         if ( str != null )
1317         {
1318             final int len = str.length();
1319             final StringBuilder builder = new StringBuilder( len );
1320             boolean uc = false;
1321 
1322             for ( int i = 0; i < len; i++ )
1323             {
1324                 final char c = str.charAt( i );
1325 
1326                 if ( builder.length() > 0 )
1327                 {
1328                     if ( Character.isJavaIdentifierPart( c ) )
1329                     {
1330                         builder.append( uc ? Character.toUpperCase( c ) : c );
1331                         uc = false;
1332                     }
1333                     else
1334                     {
1335                         uc = true;
1336                     }
1337                 }
1338                 else if ( Character.isJavaIdentifierStart( c ) )
1339                 {
1340                     builder.append( Character.toLowerCase( c ) );
1341                 }
1342             }
1343 
1344             methodParameterName = builder.toString();
1345 
1346             if ( methodParameterName.length() <= 0 && this.isLoggable( Level.WARNING ) )
1347             {
1348                 this.log( Level.WARNING, getMessage( "invalidJavaMethodParameterName", str ), null );
1349             }
1350 
1351             if ( this.getJavaKeywords().contains( methodParameterName ) )
1352             {
1353                 methodParameterName = "_" + methodParameterName;
1354             }
1355         }
1356 
1357         return methodParameterName;
1358     }
1359 
1360     /**
1361      * Gets a flag indicating the class of a given specification is located in the Java default package.
1362      *
1363      * @param specification The specification to query.
1364      *
1365      * @return {@code true}, if the class of {@code specification} is located in the Java default package;
1366      * {@code false}, else.
1367      *
1368      * @throws NullPointerException if {@code specification} is {@code null}.
1369      */
1370     public boolean isJavaDefaultPackage( final Specification specification )
1371     {
1372         if ( specification == null )
1373         {
1374             throw new NullPointerException( "specification" );
1375         }
1376 
1377         return specification.getClazz() != null && this.getJavaPackageName( specification ).length() == 0;
1378     }
1379 
1380     /**
1381      * Gets a flag indicating the class of a given implementation is located in the Java default package.
1382      *
1383      * @param implementation The implementation to query.
1384      *
1385      * @return {@code true}, if the class of {@code implementation} is located in the Java default package;
1386      * {@code false}, else.
1387      *
1388      * @throws NullPointerException if {@code implementation} is {@code null}.
1389      */
1390     public boolean isJavaDefaultPackage( final Implementation implementation )
1391     {
1392         if ( implementation == null )
1393         {
1394             throw new NullPointerException( "implementation" );
1395         }
1396 
1397         return implementation.getClazz() != null && this.getJavaPackageName( implementation ).length() == 0;
1398     }
1399 
1400     /**
1401      * Formats a string to a HTML string with HTML entities.
1402      *
1403      * @param str The string to format to a HTML string with HTML entities or {@code null}.
1404      *
1405      * @return {@code str} formatted to a HTML string with HTML entities or {@code null}.
1406      *
1407      * @see StringEscapeUtils#escapeHtml(java.lang.String)
1408      *
1409      * @since 1.2
1410      */
1411     public String getHtmlString( final String str )
1412     {
1413         return StringEscapeUtils.escapeHtml( str );
1414     }
1415 
1416     /**
1417      * Formats a string to a XML string with XML entities.
1418      *
1419      * @param str The string to format to a XML string with XML entities or {@code null}.
1420      *
1421      * @return {@code str} formatted to a XML string with XML entities or {@code null}.
1422      *
1423      * @see StringEscapeUtils#escapeXml(java.lang.String)
1424      *
1425      * @since 1.2
1426      */
1427     public String getXmlString( final String str )
1428     {
1429         return StringEscapeUtils.escapeXml( str );
1430     }
1431 
1432     /**
1433      * Formats a string to a JavaScript string applying JavaScript string rules.
1434      *
1435      * @param str The string to format to a JavaScript string by applying JavaScript string rules or {@code null}.
1436      *
1437      * @return {@code str} formatted to a JavaScript string with JavaScript string rules applied or {@code null}.
1438      *
1439      * @see StringEscapeUtils#escapeJavaScript(java.lang.String)
1440      *
1441      * @since 1.2
1442      */
1443     public String getJavaScriptString( final String str )
1444     {
1445         return StringEscapeUtils.escapeJavaScript( str );
1446     }
1447 
1448     /**
1449      * Formats a string to a SQL string.
1450      *
1451      * @param str The string to format to a SQL string or {@code null}.
1452      *
1453      * @return {@code str} formatted to a SQL string or {@code null}.
1454      *
1455      * @see StringEscapeUtils#escapeSql(java.lang.String)
1456      *
1457      * @since 1.2
1458      */
1459     public String getSqlString( final String str )
1460     {
1461         return StringEscapeUtils.escapeSql( str );
1462     }
1463 
1464     /**
1465      * Formats a string to a CSV string.
1466      *
1467      * @param str The string to format to a CSV string or {@code null}.
1468      *
1469      * @return {@code str} formatted to a CSV string or {@code null}.
1470      *
1471      * @see StringEscapeUtils#escapeCsv(java.lang.String)
1472      *
1473      * @since 1.2
1474      */
1475     public String getCsvString( final String str )
1476     {
1477         return StringEscapeUtils.escapeCsv( str );
1478     }
1479 
1480     /**
1481      * Formats a {@code Boolean} to a string.
1482      *
1483      * @param b The {@code Boolean} to format to a string or {@code null}.
1484      *
1485      * @return {@code b} formatted to a string.
1486      *
1487      * @see #getLocale()
1488      *
1489      * @since 1.2
1490      */
1491     public String getBooleanString( final Boolean b )
1492     {
1493         final MessageFormat messageFormat = new MessageFormat( ResourceBundle.getBundle(
1494             JomcTool.class.getName().replace( '.', '/' ), this.getLocale() ).
1495             getString( b ? "booleanStringTrue" : "booleanStringFalse" ), this.getLocale() );
1496 
1497         return messageFormat.format( null );
1498     }
1499 
1500     /**
1501      * Gets the display language of a given language code.
1502      *
1503      * @param language The language code to get the display language of.
1504      *
1505      * @return The display language of {@code language}.
1506      *
1507      * @throws NullPointerException if {@code language} is {@code null}.
1508      */
1509     public String getDisplayLanguage( final String language )
1510     {
1511         if ( language == null )
1512         {
1513             throw new NullPointerException( "language" );
1514         }
1515 
1516         final Locale l = new Locale( language );
1517         return l.getDisplayLanguage( l );
1518     }
1519 
1520     /**
1521      * Formats a calendar instance to a string.
1522      *
1523      * @param calendar The calendar to format to a string.
1524      *
1525      * @return The date of {@code calendar} formatted using a short format style pattern.
1526      *
1527      * @throws NullPointerException if {@code calendar} is {@code null}.
1528      *
1529      * @see DateFormat#SHORT
1530      */
1531     public String getShortDate( final Calendar calendar )
1532     {
1533         if ( calendar == null )
1534         {
1535             throw new NullPointerException( "calendar" );
1536         }
1537 
1538         return DateFormat.getDateInstance( DateFormat.SHORT, this.getLocale() ).format( calendar.getTime() );
1539     }
1540 
1541     /**
1542      * Formats a calendar instance to a string.
1543      *
1544      * @param calendar The calendar to format to a string.
1545      *
1546      * @return The date of {@code calendar} formatted using a medium format style pattern.
1547      *
1548      * @throws NullPointerException if {@code calendar} is {@code null}.
1549      *
1550      * @see DateFormat#MEDIUM
1551      *
1552      * @since 1.2
1553      */
1554     public String getMediumDate( final Calendar calendar )
1555     {
1556         if ( calendar == null )
1557         {
1558             throw new NullPointerException( "calendar" );
1559         }
1560 
1561         return DateFormat.getDateInstance( DateFormat.MEDIUM, this.getLocale() ).format( calendar.getTime() );
1562     }
1563 
1564     /**
1565      * Formats a calendar instance to a string.
1566      *
1567      * @param calendar The calendar to format to a string.
1568      *
1569      * @return The date of {@code calendar} formatted using a long format style pattern.
1570      *
1571      * @throws NullPointerException if {@code calendar} is {@code null}.
1572      *
1573      * @see DateFormat#LONG
1574      */
1575     public String getLongDate( final Calendar calendar )
1576     {
1577         if ( calendar == null )
1578         {
1579             throw new NullPointerException( "calendar" );
1580         }
1581 
1582         return DateFormat.getDateInstance( DateFormat.LONG, this.getLocale() ).format( calendar.getTime() );
1583     }
1584 
1585     /**
1586      * Formats a calendar instance to a string.
1587      *
1588      * @param calendar The calendar to format to a string.
1589      *
1590      * @return The date of {@code calendar} formatted using an ISO-8601 format style.
1591      *
1592      * @throws NullPointerException if {@code calendar} is {@code null}.
1593      *
1594      * @see SimpleDateFormat yyyy-DDD
1595      *
1596      * @since 1.2
1597      */
1598     public String getIsoDate( final Calendar calendar )
1599     {
1600         if ( calendar == null )
1601         {
1602             throw new NullPointerException( "calendar" );
1603         }
1604 
1605         return new SimpleDateFormat( "yyyy-DDD", this.getLocale() ).format( calendar.getTime() );
1606     }
1607 
1608     /**
1609      * Formats a calendar instance to a string.
1610      *
1611      * @param calendar The calendar to format to a string.
1612      *
1613      * @return The time of {@code calendar} formatted using a short format style pattern.
1614      *
1615      * @throws NullPointerException if {@code calendar} is {@code null}.
1616      *
1617      * @see DateFormat#SHORT
1618      */
1619     public String getShortTime( final Calendar calendar )
1620     {
1621         if ( calendar == null )
1622         {
1623             throw new NullPointerException( "calendar" );
1624         }
1625 
1626         return DateFormat.getTimeInstance( DateFormat.SHORT, this.getLocale() ).format( calendar.getTime() );
1627     }
1628 
1629     /**
1630      * Formats a calendar instance to a string.
1631      *
1632      * @param calendar The calendar to format to a string.
1633      *
1634      * @return The time of {@code calendar} formatted using a medium format style pattern.
1635      *
1636      * @throws NullPointerException if {@code calendar} is {@code null}.
1637      *
1638      * @see DateFormat#MEDIUM
1639      *
1640      * @since 1.2
1641      */
1642     public String getMediumTime( final Calendar calendar )
1643     {
1644         if ( calendar == null )
1645         {
1646             throw new NullPointerException( "calendar" );
1647         }
1648 
1649         return DateFormat.getTimeInstance( DateFormat.MEDIUM, this.getLocale() ).format( calendar.getTime() );
1650     }
1651 
1652     /**
1653      * Formats a calendar instance to a string.
1654      *
1655      * @param calendar The calendar to format to a string.
1656      *
1657      * @return The time of {@code calendar} formatted using a long format style pattern.
1658      *
1659      * @throws NullPointerException if {@code calendar} is {@code null}.
1660      *
1661      * @see DateFormat#LONG
1662      */
1663     public String getLongTime( final Calendar calendar )
1664     {
1665         if ( calendar == null )
1666         {
1667             throw new NullPointerException( "calendar" );
1668         }
1669 
1670         return DateFormat.getTimeInstance( DateFormat.LONG, this.getLocale() ).format( calendar.getTime() );
1671     }
1672 
1673     /**
1674      * Formats a calendar instance to a string.
1675      *
1676      * @param calendar The calendar to format to a string.
1677      *
1678      * @return The time of {@code calendar} formatted using an ISO-8601 format style.
1679      *
1680      * @throws NullPointerException if {@code calendar} is {@code null}.
1681      *
1682      * @see SimpleDateFormat HH:mm
1683      *
1684      * @since 1.2
1685      */
1686     public String getIsoTime( final Calendar calendar )
1687     {
1688         if ( calendar == null )
1689         {
1690             throw new NullPointerException( "calendar" );
1691         }
1692 
1693         return new SimpleDateFormat( "HH:mm", this.getLocale() ).format( calendar.getTime() );
1694     }
1695 
1696     /**
1697      * Formats a calendar instance to a string.
1698      *
1699      * @param calendar The calendar to format to a string.
1700      *
1701      * @return The date and time of {@code calendar} formatted using a short format style pattern.
1702      *
1703      * @throws NullPointerException if {@code calendar} is {@code null}.
1704      *
1705      * @see DateFormat#SHORT
1706      */
1707     public String getShortDateTime( final Calendar calendar )
1708     {
1709         if ( calendar == null )
1710         {
1711             throw new NullPointerException( "calendar" );
1712         }
1713 
1714         return DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT, this.getLocale() ).
1715             format( calendar.getTime() );
1716 
1717     }
1718 
1719     /**
1720      * Formats a calendar instance to a string.
1721      *
1722      * @param calendar The calendar to format to a string.
1723      *
1724      * @return The date and time of {@code calendar} formatted using a medium format style pattern.
1725      *
1726      * @throws NullPointerException if {@code calendar} is {@code null}.
1727      *
1728      * @see DateFormat#MEDIUM
1729      *
1730      * @since 1.2
1731      */
1732     public String getMediumDateTime( final Calendar calendar )
1733     {
1734         if ( calendar == null )
1735         {
1736             throw new NullPointerException( "calendar" );
1737         }
1738 
1739         return DateFormat.getDateTimeInstance( DateFormat.MEDIUM, DateFormat.MEDIUM, this.getLocale() ).
1740             format( calendar.getTime() );
1741 
1742     }
1743 
1744     /**
1745      * Formats a calendar instance to a string.
1746      *
1747      * @param calendar The calendar to format to a string.
1748      *
1749      * @return The date and time of {@code calendar} formatted using a long format style pattern.
1750      *
1751      * @throws NullPointerException if {@code calendar} is {@code null}.
1752      *
1753      * @see DateFormat#LONG
1754      */
1755     public String getLongDateTime( final Calendar calendar )
1756     {
1757         if ( calendar == null )
1758         {
1759             throw new NullPointerException( "calendar" );
1760         }
1761 
1762         return DateFormat.getDateTimeInstance( DateFormat.LONG, DateFormat.LONG, this.getLocale() ).
1763             format( calendar.getTime() );
1764 
1765     }
1766 
1767     /**
1768      * Formats a calendar instance to a string.
1769      *
1770      * @param calendar The calendar to format to a string.
1771      *
1772      * @return The date and time of {@code calendar} formatted using a ISO-8601 format style.
1773      *
1774      * @throws NullPointerException if {@code calendar} is {@code null}.
1775      *
1776      * @see SimpleDateFormat yyyy-MM-dd'T'HH:mm:ssZ
1777      *
1778      * @since 1.2
1779      */
1780     public String getIsoDateTime( final Calendar calendar )
1781     {
1782         if ( calendar == null )
1783         {
1784             throw new NullPointerException( "calendar" );
1785         }
1786 
1787         // JDK: As of JDK 7, "yyyy-MM-dd'T'HH:mm:ssXXX".
1788         return new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ssZ", this.getLocale() ).format( calendar.getTime() );
1789     }
1790 
1791     /**
1792      * Gets a string describing the range of years for given calendars.
1793      *
1794      * @param start The start of the range.
1795      * @param end The end of the range.
1796      *
1797      * @return Formatted range of the years of {@code start} and {@code end} (e.g. {@code "start - end"}).
1798      *
1799      * @throws NullPointerException if {@code start} or {@code end} is {@code null}.
1800      */
1801     public String getYears( final Calendar start, final Calendar end )
1802     {
1803         if ( start == null )
1804         {
1805             throw new NullPointerException( "start" );
1806         }
1807         if ( end == null )
1808         {
1809             throw new NullPointerException( "end" );
1810         }
1811 
1812         final Format yearFormat = new SimpleDateFormat( "yyyy", this.getLocale() );
1813         final int s = start.get( Calendar.YEAR );
1814         final int e = end.get( Calendar.YEAR );
1815         final StringBuilder years = new StringBuilder();
1816 
1817         if ( s != e )
1818         {
1819             if ( s < e )
1820             {
1821                 years.append( yearFormat.format( start.getTime() ) ).append( " - " ).
1822                     append( yearFormat.format( end.getTime() ) );
1823 
1824             }
1825             else
1826             {
1827                 years.append( yearFormat.format( end.getTime() ) ).append( " - " ).
1828                     append( yearFormat.format( start.getTime() ) );
1829 
1830             }
1831         }
1832         else
1833         {
1834             years.append( yearFormat.format( start.getTime() ) );
1835         }
1836 
1837         return years.toString();
1838     }
1839 
1840     /**
1841      * Gets the model of the instance.
1842      *
1843      * @return The model of the instance.
1844      *
1845      * @see #getModules()
1846      * @see #setModel(org.jomc.modlet.Model)
1847      */
1848     public final Model getModel()
1849     {
1850         if ( this.model == null )
1851         {
1852             this.model = new Model();
1853             this.model.setIdentifier( ModelObject.MODEL_PUBLIC_ID );
1854         }
1855 
1856         return this.model;
1857     }
1858 
1859     /**
1860      * Sets the model of the instance.
1861      *
1862      * @param value The new model of the instance or {@code null}.
1863      *
1864      * @see #getModel()
1865      */
1866     public final void setModel( final Model value )
1867     {
1868         this.model = value;
1869     }
1870 
1871     /**
1872      * Gets the modules of the instance.
1873      *
1874      * @return The modules of the instance.
1875      *
1876      * @see #getModel()
1877      * @see #setModel(org.jomc.modlet.Model)
1878      *
1879      * @deprecated As of JOMC 1.2, please use method {@link #getModel()} and {@link ModelHelper#getModules(org.jomc.modlet.Model)}.
1880      * This method will be removed in version 2.0.
1881      */
1882     @Deprecated
1883     public Modules getModules()
1884     {
1885         Modules modules = ModelHelper.getModules( this.getModel() );
1886 
1887         if ( modules == null )
1888         {
1889             modules = new Modules();
1890             ModelHelper.setModules( this.getModel(), modules );
1891         }
1892 
1893         return modules;
1894     }
1895 
1896     /**
1897      * Gets the {@code VelocityEngine} of the instance.
1898      *
1899      * @return The {@code VelocityEngine} of the instance.
1900      *
1901      * @throws IOException if initializing a new velocity engine fails.
1902      *
1903      * @see #setVelocityEngine(org.apache.velocity.app.VelocityEngine)
1904      */
1905     public final VelocityEngine getVelocityEngine() throws IOException
1906     {
1907         if ( this.velocityEngine == null )
1908         {
1909             /** {@code LogChute} logging to the listeners of the tool. */
1910             class JomcLogChute implements LogChute
1911             {
1912 
1913                 JomcLogChute()
1914                 {
1915                     super();
1916                 }
1917 
1918                 public void init( final RuntimeServices runtimeServices ) throws Exception
1919                 {
1920                 }
1921 
1922                 public void log( final int level, final String message )
1923                 {
1924                     this.log( level, message, null );
1925                 }
1926 
1927                 public void log( final int level, final String message, final Throwable throwable )
1928                 {
1929                     JomcTool.this.log( Level.FINEST, message, throwable );
1930                 }
1931 
1932                 public boolean isLevelEnabled( final int level )
1933                 {
1934                     return isLoggable( Level.FINEST );
1935                 }
1936 
1937             }
1938 
1939             final VelocityEngine engine = new VelocityEngine();
1940             engine.setProperty( RuntimeConstants.RUNTIME_REFERENCES_STRICT, Boolean.TRUE.toString() );
1941             engine.setProperty( RuntimeConstants.VM_ARGUMENTS_STRICT, Boolean.TRUE.toString() );
1942             engine.setProperty( RuntimeConstants.STRICT_MATH, Boolean.TRUE.toString() );
1943             engine.setProperty( RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new JomcLogChute() );
1944 
1945             engine.setProperty( RuntimeConstants.RESOURCE_LOADER, "class" );
1946             engine.setProperty( "class.resource.loader.class", ClasspathResourceLoader.class.getName() );
1947             engine.setProperty( "class.resource.loader.cache", Boolean.TRUE.toString() );
1948 
1949             if ( this.getTemplateLocation() != null )
1950             {
1951                 engine.setProperty( RuntimeConstants.RESOURCE_LOADER, "class,url" );
1952                 engine.setProperty( "url.resource.loader.class", URLResourceLoader.class.getName() );
1953                 engine.setProperty( "url.resource.loader.cache", Boolean.TRUE.toString() );
1954                 engine.setProperty( "url.resource.loader.root", this.getTemplateLocation().toExternalForm() );
1955                 engine.setProperty( "url.resource.loader.timeout", Integer.toString( 60000 ) );
1956             }
1957 
1958             this.velocityEngine = engine;
1959         }
1960 
1961         return this.velocityEngine;
1962     }
1963 
1964     /**
1965      * Sets the {@code VelocityEngine} of the instance.
1966      *
1967      * @param value The new {@code VelocityEngine} of the instance or {@code null}.
1968      *
1969      * @see #getVelocityEngine()
1970      */
1971     public final void setVelocityEngine( final VelocityEngine value )
1972     {
1973         this.velocityEngine = value;
1974     }
1975 
1976     /**
1977      * Gets a new velocity context used for merging templates.
1978      *
1979      * @return A new velocity context used for merging templates.
1980      *
1981      * @see #getTemplateParameters()
1982      */
1983     public VelocityContext getVelocityContext()
1984     {
1985         final Calendar now = Calendar.getInstance();
1986         final VelocityContext ctx = new VelocityContext( Collections.synchronizedMap(
1987             new HashMap<String, Object>( this.getTemplateParameters() ) ) );
1988 
1989         this.mergeTemplateProfileProperties( this.getTemplateProfile(), this.getLocale().getLanguage(), ctx );
1990         this.mergeTemplateProfileProperties( this.getTemplateProfile(), null, ctx );
1991         this.mergeTemplateProfileProperties( getDefaultTemplateProfile(), this.getLocale().getLanguage(), ctx );
1992         this.mergeTemplateProfileProperties( getDefaultTemplateProfile(), null, ctx );
1993 
1994         this.getModules(); // Initialization prior to cloning.
1995         final Model clonedModel = this.getModel().clone();
1996         final Modules clonedModules = ModelHelper.getModules( clonedModel );
1997         assert clonedModules != null : "Unexpected missing modules for model '" + clonedModel.getIdentifier() + "'.";
1998 
1999         ctx.put( "model", clonedModel );
2000         ctx.put( "modules", clonedModules );
2001         ctx.put( "imodel", new InheritanceModel( this.getModules() ) );
2002         ctx.put( "tool", this );
2003         ctx.put( "toolName", this.getClass().getName() );
2004         ctx.put( "toolVersion", getMessage( "projectVersion" ) );
2005         ctx.put( "toolUrl", getMessage( "projectUrl" ) );
2006         ctx.put( "calendar", now.getTime() );
2007 
2008         // JDK: As of JDK 7, "yyyy-MM-dd'T'HH:mm:ss.SSSXXX".
2009         ctx.put( "now",
2010                  new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ", this.getLocale() ).format( now.getTime() ) );
2011 
2012         ctx.put( "year", new SimpleDateFormat( "yyyy", this.getLocale() ).format( now.getTime() ) );
2013         ctx.put( "month", new SimpleDateFormat( "MM", this.getLocale() ).format( now.getTime() ) );
2014         ctx.put( "day", new SimpleDateFormat( "dd", this.getLocale() ).format( now.getTime() ) );
2015         ctx.put( "hour", new SimpleDateFormat( "HH", this.getLocale() ).format( now.getTime() ) );
2016         ctx.put( "minute", new SimpleDateFormat( "mm", this.getLocale() ).format( now.getTime() ) );
2017         ctx.put( "second", new SimpleDateFormat( "ss", this.getLocale() ).format( now.getTime() ) );
2018         ctx.put( "timezone", new SimpleDateFormat( "Z", this.getLocale() ).format( now.getTime() ) );
2019         ctx.put( "shortDate", this.getShortDate( now ) );
2020         ctx.put( "mediumDate", this.getMediumDate( now ) );
2021         ctx.put( "longDate", this.getLongDate( now ) );
2022         ctx.put( "isoDate", this.getIsoDate( now ) );
2023         ctx.put( "shortTime", this.getShortTime( now ) );
2024         ctx.put( "mediumTime", this.getMediumTime( now ) );
2025         ctx.put( "longTime", this.getLongTime( now ) );
2026         ctx.put( "isoTime", this.getIsoTime( now ) );
2027         ctx.put( "shortDateTime", this.getShortDateTime( now ) );
2028         ctx.put( "mediumDateTime", this.getMediumDateTime( now ) );
2029         ctx.put( "longDateTime", this.getLongDateTime( now ) );
2030         ctx.put( "isoDateTime", this.getIsoDateTime( now ) );
2031 
2032         return ctx;
2033     }
2034 
2035     /**
2036      * Gets the template parameters of the instance.
2037      * <p>This accessor method returns a reference to the live map, not a snapshot. Therefore any modification you make
2038      * to the returned map will be present inside the object. This is why there is no {@code set} method for the
2039      * template parameters property.</p>
2040      *
2041      * @return The template parameters of the instance.
2042      *
2043      * @see #getVelocityContext()
2044      *
2045      * @since 1.2
2046      */
2047     public final Map<String, Object> getTemplateParameters()
2048     {
2049         if ( this.templateParameters == null )
2050         {
2051             this.templateParameters = new HashMap<String, Object>();
2052         }
2053 
2054         return this.templateParameters;
2055     }
2056 
2057     /**
2058      * Gets the location to search for templates in addition to searching the class path.
2059      *
2060      * @return The location to search for templates in addition to searching the class path or {@code null}.
2061      *
2062      * @see #setTemplateLocation(java.net.URL)
2063      *
2064      * @since 1.2
2065      */
2066     public final URL getTemplateLocation()
2067     {
2068         return this.templateLocation;
2069     }
2070 
2071     /**
2072      * Sets the location to search for templates in addition to searching the class path.
2073      *
2074      * @param value The new location to search for templates in addition to searching the class path or {@code null}.
2075      *
2076      * @see #getTemplateLocation()
2077      *
2078      * @since 1.2
2079      */
2080     public final void setTemplateLocation( final URL value )
2081     {
2082         this.templateLocation = value;
2083     }
2084 
2085     /**
2086      * Gets the encoding to use for reading templates.
2087      *
2088      * @return The encoding to use for reading templates.
2089      *
2090      * @see #setTemplateEncoding(java.lang.String)
2091      */
2092     public final String getTemplateEncoding()
2093     {
2094         if ( this.templateEncoding == null )
2095         {
2096             this.templateEncoding = getMessage( "buildSourceEncoding" );
2097 
2098             if ( this.isLoggable( Level.CONFIG ) )
2099             {
2100                 this.log( Level.CONFIG, getMessage( "defaultTemplateEncoding", this.templateEncoding ), null );
2101             }
2102         }
2103 
2104         return this.templateEncoding;
2105     }
2106 
2107     /**
2108      * Sets the encoding to use for reading templates.
2109      *
2110      * @param value The new encoding to use for reading templates or {@code null}.
2111      *
2112      * @see #getTemplateEncoding()
2113      */
2114     public final void setTemplateEncoding( final String value )
2115     {
2116         this.templateEncoding = value;
2117         this.velocityEngine = null;
2118     }
2119 
2120     /**
2121      * Gets the encoding to use for reading files.
2122      *
2123      * @return The encoding to use for reading files.
2124      *
2125      * @see #setInputEncoding(java.lang.String)
2126      */
2127     public final String getInputEncoding()
2128     {
2129         if ( this.inputEncoding == null )
2130         {
2131             this.inputEncoding = new InputStreamReader( new ByteArrayInputStream( NO_BYTES ) ).getEncoding();
2132 
2133             if ( this.isLoggable( Level.CONFIG ) )
2134             {
2135                 this.log( Level.CONFIG, getMessage( "defaultInputEncoding", this.inputEncoding ), null );
2136             }
2137         }
2138 
2139         return this.inputEncoding;
2140     }
2141 
2142     /**
2143      * Sets the encoding to use for reading files.
2144      *
2145      * @param value The new encoding to use for reading files or {@code null}.
2146      *
2147      * @see #getInputEncoding()
2148      */
2149     public final void setInputEncoding( final String value )
2150     {
2151         this.inputEncoding = value;
2152     }
2153 
2154     /**
2155      * Gets the encoding to use for writing files.
2156      *
2157      * @return The encoding to use for writing files.
2158      *
2159      * @see #setOutputEncoding(java.lang.String)
2160      */
2161     public final String getOutputEncoding()
2162     {
2163         if ( this.outputEncoding == null )
2164         {
2165             this.outputEncoding = new OutputStreamWriter( new ByteArrayOutputStream() ).getEncoding();
2166 
2167             if ( this.isLoggable( Level.CONFIG ) )
2168             {
2169                 this.log( Level.CONFIG, getMessage( "defaultOutputEncoding", this.outputEncoding ), null );
2170             }
2171         }
2172 
2173         return this.outputEncoding;
2174     }
2175 
2176     /**
2177      * Sets the encoding to use for writing files.
2178      *
2179      * @param value The encoding to use for writing files or {@code null}.
2180      *
2181      * @see #getOutputEncoding()
2182      */
2183     public final void setOutputEncoding( final String value )
2184     {
2185         this.outputEncoding = value;
2186     }
2187 
2188     /**
2189      * Gets the default template profile.
2190      * <p>The default template profile is controlled by system property
2191      * {@code org.jomc.tools.JomcTool.defaultTemplateProfile} holding the name of the template profile to use by
2192      * default. If that property is not set, the {@code jomc-java} default is returned.</p>
2193      *
2194      * @return The default template profile.
2195      *
2196      * @see #setDefaultTemplateProfile(java.lang.String)
2197      *
2198      * @deprecated The {@code static} modifier of this method and support to setup the default template profile using
2199      * a system property will be removed in version 2.0.
2200      */
2201     @Deprecated
2202     public static String getDefaultTemplateProfile()
2203     {
2204         if ( defaultTemplateProfile == null )
2205         {
2206             defaultTemplateProfile = System.getProperty( "org.jomc.tools.JomcTool.defaultTemplateProfile",
2207                                                          DEFAULT_TEMPLATE_PROFILE );
2208 
2209         }
2210 
2211         return defaultTemplateProfile;
2212     }
2213 
2214     /**
2215      * Sets the default template profile.
2216      *
2217      * @param value The new default template profile or {@code null}.
2218      *
2219      * @see #getDefaultTemplateProfile()
2220      *
2221      * @deprecated The {@code static} modifier of this method will be removed in version 2.0.
2222      */
2223     @Deprecated
2224     public static void setDefaultTemplateProfile( final String value )
2225     {
2226         defaultTemplateProfile = value;
2227     }
2228 
2229     /**
2230      * Gets the template profile of the instance.
2231      *
2232      * @return The template profile of the instance.
2233      *
2234      * @see #getDefaultTemplateProfile()
2235      * @see #setTemplateProfile(java.lang.String)
2236      */
2237     public final String getTemplateProfile()
2238     {
2239         if ( this.templateProfile == null )
2240         {
2241             this.templateProfile = getDefaultTemplateProfile();
2242 
2243             if ( this.isLoggable( Level.CONFIG ) )
2244             {
2245                 this.log( Level.CONFIG, getMessage( "defaultTemplateProfile", this.templateProfile ), null );
2246             }
2247         }
2248 
2249         return this.templateProfile;
2250     }
2251 
2252     /**
2253      * Sets the template profile of the instance.
2254      *
2255      * @param value The new template profile of the instance or {@code null}.
2256      *
2257      * @see #getTemplateProfile()
2258      */
2259     public final void setTemplateProfile( final String value )
2260     {
2261         this.templateProfile = value;
2262     }
2263 
2264     /**
2265      * Gets the indentation string of the instance.
2266      *
2267      * @return The indentation string of the instance.
2268      *
2269      * @see #setIndentation(java.lang.String)
2270      */
2271     public final String getIndentation()
2272     {
2273         if ( this.indentation == null )
2274         {
2275             this.indentation = "    ";
2276 
2277             if ( this.isLoggable( Level.CONFIG ) )
2278             {
2279                 this.log( Level.CONFIG, getMessage( "defaultIndentation",
2280                                                     StringEscapeUtils.escapeJava( this.indentation ) ), null );
2281 
2282             }
2283         }
2284 
2285         return this.indentation;
2286     }
2287 
2288     /**
2289      * Gets an indentation string for a given indentation level.
2290      *
2291      * @param level The indentation level to get an indentation string for.
2292      *
2293      * @return The indentation string for {@code level}.
2294      *
2295      * @throws IllegalArgumentException if {@code level} is negative.
2296      *
2297      * @see #getIndentation()
2298      */
2299     public final String getIndentation( final int level )
2300     {
2301         if ( level < 0 )
2302         {
2303             throw new IllegalArgumentException( Integer.toString( level ) );
2304         }
2305 
2306         Map<String, String> map = this.indentationCache == null ? null : this.indentationCache.get();
2307 
2308         if ( map == null )
2309         {
2310             map = new HashMap<String, String>();
2311             this.indentationCache = new SoftReference<Map<String, String>>( map );
2312         }
2313 
2314         final String key = this.getIndentation() + "|" + level;
2315         String idt = map.get( key );
2316 
2317         if ( idt == null )
2318         {
2319             final StringBuilder b = new StringBuilder( this.getIndentation().length() * level );
2320 
2321             for ( int i = level; i > 0; i-- )
2322             {
2323                 b.append( this.getIndentation() );
2324             }
2325 
2326             idt = b.toString();
2327             map.put( key, idt );
2328         }
2329 
2330         return idt;
2331     }
2332 
2333     /**
2334      * Sets the indentation string of the instance.
2335      *
2336      * @param value The new indentation string of the instance or {@code null}.
2337      *
2338      * @see #getIndentation()
2339      */
2340     public final void setIndentation( final String value )
2341     {
2342         this.indentation = value;
2343     }
2344 
2345     /**
2346      * Gets the line separator of the instance.
2347      *
2348      * @return The line separator of the instance.
2349      *
2350      * @see #setLineSeparator(java.lang.String)
2351      */
2352     public final String getLineSeparator()
2353     {
2354         if ( this.lineSeparator == null )
2355         {
2356             this.lineSeparator = System.getProperty( "line.separator", "\n" );
2357 
2358             if ( this.isLoggable( Level.CONFIG ) )
2359             {
2360                 this.log( Level.CONFIG, getMessage( "defaultLineSeparator",
2361                                                     StringEscapeUtils.escapeJava( this.lineSeparator ) ), null );
2362 
2363             }
2364         }
2365 
2366         return this.lineSeparator;
2367     }
2368 
2369     /**
2370      * Sets the line separator of the instance.
2371      *
2372      * @param value The new line separator of the instance or {@code null}.
2373      *
2374      * @see #getLineSeparator()
2375      */
2376     public final void setLineSeparator( final String value )
2377     {
2378         this.lineSeparator = value;
2379     }
2380 
2381     /**
2382      * Gets the locale of the instance.
2383      *
2384      * @return The locale of the instance.
2385      *
2386      * @see #setLocale(java.util.Locale)
2387      *
2388      * @since 1.2
2389      */
2390     public final Locale getLocale()
2391     {
2392         if ( this.locale == null )
2393         {
2394             this.locale = Locale.ENGLISH;
2395 
2396             if ( this.isLoggable( Level.CONFIG ) )
2397             {
2398                 this.log( Level.CONFIG, getMessage( "defaultLocale", this.locale ), null );
2399             }
2400         }
2401 
2402         return this.locale;
2403     }
2404 
2405     /**
2406      * Sets the locale of the instance.
2407      *
2408      * @param value The new locale of the instance or {@code null}.
2409      *
2410      * @see #getLocale()
2411      *
2412      * @since 1.2
2413      */
2414     public final void setLocale( final Locale value )
2415     {
2416         this.locale = value;
2417     }
2418 
2419     /**
2420      * Gets a velocity template for a given name.
2421      * <p>This method searches templates at the following locations in the shown order.
2422      * <ol>
2423      *  <li><code>org/jomc/tools/templates/{@link #getTemplateProfile() profile}/{@link #getLocale() language}/<i>templateName</i></code></li>
2424      *  <li><code>org/jomc/tools/templates/{@link #getTemplateProfile() profile}/<i>templateName</i></code></li>
2425      *  <li><code>org/jomc/tools/templates/{@link #getDefaultTemplateProfile() default profile}/{@link #getLocale() language}/<i>templateName</i></code></li>
2426      *  <li><code>org/jomc/tools/templates/{@link #getDefaultTemplateProfile() default profile}/<i>templateName</i></code></li>
2427      * </ol></p>
2428      *
2429      * @param templateName The name of the template to get.
2430      *
2431      * @return The template matching {@code templateName}.
2432      *
2433      * @throws NullPointerException if {@code templateName} is {@code null}.
2434      * @throws IOException if getting the template fails.
2435      *
2436      * @see #getLocale()
2437      * @see #getTemplateProfile()
2438      * @see #getTemplateEncoding()
2439      * @see #getVelocityEngine()
2440      */
2441     public Template getVelocityTemplate( final String templateName ) throws IOException
2442     {
2443         if ( templateName == null )
2444         {
2445             throw new NullPointerException( "templateName" );
2446         }
2447 
2448         String location = null;
2449         Template template = null;
2450         final String key = this.getLocale() + "|" + this.getTemplateProfile() + "|" + getDefaultTemplateProfile()
2451                            + "|" + templateName;
2452 
2453         Map<String, String> map = this.templateLocationsCache == null ? null : this.templateLocationsCache.get();
2454 
2455         if ( map == null )
2456         {
2457             map = new HashMap<String, String>( 32 );
2458             this.templateLocationsCache = new SoftReference<Map<String, String>>( map );
2459         }
2460 
2461         location = map.get( key );
2462 
2463         if ( location == null && !map.containsKey( key ) )
2464         {
2465             if ( !StringUtils.EMPTY.equals( this.getLocale().getLanguage() ) )
2466             {
2467                 location = TEMPLATE_PREFIX + this.getTemplateProfile() + "/" + this.getLocale().getLanguage() + "/"
2468                            + templateName;
2469 
2470                 template = this.findVelocityTemplate( location );
2471             }
2472 
2473             if ( template == null )
2474             {
2475                 location = TEMPLATE_PREFIX + this.getTemplateProfile() + "/" + templateName;
2476                 template = this.findVelocityTemplate( location );
2477             }
2478 
2479             if ( template == null && !StringUtils.EMPTY.equals( this.getLocale().getLanguage() ) )
2480             {
2481                 location = TEMPLATE_PREFIX + getDefaultTemplateProfile() + "/" + this.getLocale().getLanguage() + "/"
2482                            + templateName;
2483 
2484                 template = this.findVelocityTemplate( location );
2485             }
2486 
2487             if ( template == null )
2488             {
2489                 location = TEMPLATE_PREFIX + getDefaultTemplateProfile() + "/" + templateName;
2490                 template = this.findVelocityTemplate( location );
2491             }
2492 
2493             map.put( key, location );
2494         }
2495         else if ( location != null )
2496         {
2497             template = this.findVelocityTemplate( location );
2498         }
2499 
2500         if ( template == null )
2501         {
2502             throw new IOException( getMessage( "noSuchTemplate", templateName ) );
2503         }
2504 
2505         if ( this.isLoggable( Level.FINER ) )
2506         {
2507             this.log( Level.FINER, getMessage( "templateInfo", templateName, location ), null );
2508         }
2509 
2510         return template;
2511     }
2512 
2513     /**
2514      * Notifies registered listeners.
2515      *
2516      * @param level The level of the event.
2517      * @param message The message of the event or {@code null}.
2518      * @param throwable The throwable of the event or {@code null}.
2519      *
2520      * @throws NullPointerException if {@code level} is {@code null}.
2521      *
2522      * @see #getListeners()
2523      * @see #isLoggable(java.util.logging.Level)
2524      */
2525     public void log( final Level level, final String message, final Throwable throwable )
2526     {
2527         if ( level == null )
2528         {
2529             throw new NullPointerException( "level" );
2530         }
2531 
2532         if ( this.isLoggable( level ) )
2533         {
2534             for ( int i = this.getListeners().size() - 1; i >= 0; i-- )
2535             {
2536                 this.getListeners().get( i ).onLog( level, message, throwable );
2537             }
2538         }
2539     }
2540 
2541     private String getJavaPackageName( final String identifier )
2542     {
2543         if ( identifier == null )
2544         {
2545             throw new NullPointerException( "identifier" );
2546         }
2547 
2548         final int idx = identifier.lastIndexOf( '.' );
2549         return idx != -1 ? identifier.substring( 0, idx ) : "";
2550     }
2551 
2552     private Template findVelocityTemplate( final String location ) throws IOException
2553     {
2554         try
2555         {
2556             return this.getVelocityEngine().getTemplate( location, this.getTemplateEncoding() );
2557         }
2558         catch ( final ResourceNotFoundException e )
2559         {
2560             if ( this.isLoggable( Level.FINER ) )
2561             {
2562                 this.log( Level.FINER, getMessage( "templateNotFound", location ), null );
2563             }
2564 
2565             return null;
2566         }
2567         catch ( final ParseErrorException e )
2568         {
2569             String m = getMessage( e );
2570             m = m == null ? "" : " " + m;
2571 
2572             // JDK: As of JDK 6, "new IOException( message, cause )".
2573             throw (IOException) new IOException( getMessage( "invalidTemplate", location, m ) ).initCause( e );
2574         }
2575         catch ( final VelocityException e )
2576         {
2577             String m = getMessage( e );
2578             m = m == null ? "" : " " + m;
2579 
2580             // JDK: As of JDK 6, "new IOException( message, cause )".
2581             throw (IOException) new IOException( getMessage( "velocityException", location, m ) ).initCause( e );
2582         }
2583     }
2584 
2585     private java.util.Properties getTemplateProfileProperties( final String profileName, final String language )
2586     {
2587         Map<String, java.util.Properties> map =
2588             this.templateProfilePropertiesCache == null ? null : this.templateProfilePropertiesCache.get();
2589 
2590         if ( map == null )
2591         {
2592             map = new HashMap<String, java.util.Properties>();
2593             this.templateProfilePropertiesCache = new SoftReference<Map<String, java.util.Properties>>( map );
2594         }
2595 
2596         final String key = profileName + "|" + language;
2597         java.util.Properties profileProperties = map.get( key );
2598 
2599         if ( profileProperties == null )
2600         {
2601             InputStream in = null;
2602             profileProperties = new java.util.Properties();
2603             final String resourceName = "/" + TEMPLATE_PREFIX + profileName + ( language == null ? "" : "/" + language )
2604                                         + "/context.properties";
2605 
2606             try
2607             {
2608                 in = this.getClass().getResourceAsStream( resourceName );
2609 
2610                 if ( in != null )
2611                 {
2612                     if ( this.isLoggable( Level.CONFIG ) )
2613                     {
2614                         this.log( Level.CONFIG, getMessage( "contextPropertiesFound", resourceName ), null );
2615                     }
2616 
2617                     profileProperties.load( in );
2618                 }
2619                 else if ( this.isLoggable( Level.CONFIG ) )
2620                 {
2621                     this.log( Level.CONFIG, getMessage( "contextPropertiesNotFound", resourceName ), null );
2622                 }
2623 
2624                 map.put( key, profileProperties );
2625             }
2626             catch ( final IOException e )
2627             {
2628                 this.log( Level.SEVERE, getMessage( e ), e );
2629             }
2630             finally
2631             {
2632                 try
2633                 {
2634                     if ( in != null )
2635                     {
2636                         in.close();
2637                     }
2638                 }
2639                 catch ( final IOException e )
2640                 {
2641                     this.log( Level.SEVERE, getMessage( e ), e );
2642                 }
2643             }
2644         }
2645 
2646         return profileProperties;
2647     }
2648 
2649     private void mergeTemplateProfileProperties( final String profileName, final String language,
2650                                                  final VelocityContext velocityContext )
2651     {
2652         final java.util.Properties templateProfileProperties =
2653             this.getTemplateProfileProperties( profileName, language );
2654 
2655         for ( final Enumeration<?> e = templateProfileProperties.propertyNames(); e.hasMoreElements(); )
2656         {
2657             final String name = e.nextElement().toString();
2658             final String value = templateProfileProperties.getProperty( name );
2659             final String[] values = value.split( "\\|" );
2660 
2661             if ( !velocityContext.containsKey( name ) )
2662             {
2663                 try
2664                 {
2665                     if ( values.length > 1 )
2666                     {
2667                         final Class<?> valueClass = Class.forName( values[0] );
2668                         velocityContext.put( name, valueClass.getConstructor( String.class ).newInstance( values[1] ) );
2669                     }
2670                     else if ( value.contains( "|" ) )
2671                     {
2672                         velocityContext.put( name, Class.forName( values[0] ).newInstance() );
2673                     }
2674                     else
2675                     {
2676                         velocityContext.put( name, value );
2677                     }
2678                 }
2679                 catch ( final InstantiationException ex )
2680                 {
2681                     this.log( Level.SEVERE, getMessage( ex ), ex );
2682                 }
2683                 catch ( final IllegalAccessException ex )
2684                 {
2685                     this.log( Level.SEVERE, getMessage( ex ), ex );
2686                 }
2687                 catch ( final InvocationTargetException ex )
2688                 {
2689                     this.log( Level.SEVERE, getMessage( ex ), ex );
2690                 }
2691                 catch ( final NoSuchMethodException ex )
2692                 {
2693                     this.log( Level.SEVERE, getMessage( ex ), ex );
2694                 }
2695                 catch ( final ClassNotFoundException ex )
2696                 {
2697                     this.log( Level.SEVERE, getMessage( ex ), ex );
2698                 }
2699             }
2700         }
2701     }
2702 
2703     private Set<String> getJavaKeywords()
2704     {
2705         Reader in = null;
2706         Set<String> set = this.javaKeywordsCache == null ? null : this.javaKeywordsCache.get();
2707 
2708         try
2709         {
2710             if ( set == null )
2711             {
2712                 set = new HashSet<String>( 64 );
2713                 this.javaKeywordsCache = new SoftReference<Set<String>>( set );
2714             }
2715 
2716             in = new InputStreamReader( this.getClass().getResourceAsStream(
2717                 "/" + this.getClass().getPackage().getName().replace( ".", "/" ) + "/JavaKeywords.txt" ), "UTF-8" );
2718 
2719             set.addAll( IOUtils.readLines( in ) );
2720         }
2721         catch ( final IOException e )
2722         {
2723             throw new IllegalStateException( getMessage( e ), e );
2724         }
2725         finally
2726         {
2727             try
2728             {
2729                 if ( in != null )
2730                 {
2731                     in.close();
2732                 }
2733             }
2734             catch ( final IOException e )
2735             {
2736                 throw new IllegalStateException( getMessage( e ), e );
2737             }
2738         }
2739 
2740         return set;
2741     }
2742 
2743     private static String getMessage( final String key, final Object... arguments )
2744     {
2745         return MessageFormat.format( ResourceBundle.getBundle(
2746             JomcTool.class.getName().replace( '.', '/' ) ).getString( key ), arguments );
2747 
2748     }
2749 
2750     private static String getMessage( final Throwable t )
2751     {
2752         return t != null ? t.getMessage() != null ? t.getMessage() : getMessage( t.getCause() ) : null;
2753     }
2754 
2755 }