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