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