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