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