001    /*
002     *   Copyright (c) 2009 The JOMC Project
003     *   Copyright (c) 2005 Christian Schulte <cs@jomc.org>
004     *   All rights reserved.
005     *
006     *   Redistribution and use in source and binary forms, with or without
007     *   modification, are permitted provided that the following conditions
008     *   are met:
009     *
010     *     o Redistributions of source code must retain the above copyright
011     *       notice, this list of conditions and the following disclaimer.
012     *
013     *     o Redistributions in binary form must reproduce the above copyright
014     *       notice, this list of conditions and the following disclaimer in
015     *       the documentation and/or other materials provided with the
016     *       distribution.
017     *
018     *   THIS SOFTWARE IS PROVIDED BY THE JOMC PROJECT AND CONTRIBUTORS "AS IS"
019     *   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020     *   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021     *   PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE JOMC PROJECT OR
022     *   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023     *   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024     *   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025     *   OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026     *   WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
027     *   OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
028     *   ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029     *
030     *   $Id: JomcTool.java 1087 2009-12-05 23:36:48Z schulte2005 $
031     *
032     */
033    package org.jomc.tools;
034    
035    import java.io.ByteArrayInputStream;
036    import java.io.ByteArrayOutputStream;
037    import java.io.IOException;
038    import java.io.InputStreamReader;
039    import java.io.OutputStreamWriter;
040    import java.text.DateFormat;
041    import java.text.Format;
042    import java.text.MessageFormat;
043    import java.text.SimpleDateFormat;
044    import java.util.ArrayList;
045    import java.util.Calendar;
046    import java.util.Date;
047    import java.util.HashMap;
048    import java.util.LinkedList;
049    import java.util.List;
050    import java.util.Locale;
051    import java.util.Map;
052    import java.util.ResourceBundle;
053    import java.util.logging.Level;
054    import org.apache.commons.lang.StringEscapeUtils;
055    import org.apache.velocity.Template;
056    import org.apache.velocity.VelocityContext;
057    import org.apache.velocity.app.VelocityEngine;
058    import org.apache.velocity.exception.ResourceNotFoundException;
059    import org.apache.velocity.runtime.RuntimeConstants;
060    import org.apache.velocity.runtime.RuntimeServices;
061    import org.apache.velocity.runtime.log.LogChute;
062    import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
063    import org.jomc.model.Argument;
064    import org.jomc.model.ArgumentType;
065    import org.jomc.model.Dependency;
066    import org.jomc.model.Implementation;
067    import org.jomc.model.Message;
068    import org.jomc.model.Modules;
069    import org.jomc.model.Multiplicity;
070    import org.jomc.model.Properties;
071    import org.jomc.model.Property;
072    import org.jomc.model.Specification;
073    import org.jomc.model.SpecificationReference;
074    import org.jomc.model.Specifications;
075    import org.jomc.model.Text;
076    
077    /**
078     * Base tool class.
079     *
080     * @author <a href="mailto:cs@jomc.org">Christian Schulte</a>
081     * @version $Id: JomcTool.java 1087 2009-12-05 23:36:48Z schulte2005 $
082     */
083    public abstract class JomcTool
084    {
085    
086        /** Listener interface. */
087        public interface Listener
088        {
089    
090            /**
091             * Get called on logging.
092             *
093             * @param level The level of the event.
094             * @param message The message of the event or {@code null}.
095             * @param throwable The throwable of the event or {@code null}.
096             *
097             * @throws NullPointerException if {@code level} is {@code null}.
098             */
099            void onLog( Level level, String message, Throwable throwable );
100    
101        }
102    
103        /** Empty byte array. */
104        private static final byte[] NO_BYTES =
105        {
106        };
107    
108        /** The prefix of the template location. */
109        private static final String TEMPLATE_PREFIX =
110            JomcTool.class.getPackage().getName().replace( '.', '/' ) + "/templates/";
111    
112        /** Name of the velocity classpath resource loader implementation. */
113        private static final String VELOCITY_RESOURCE_LOADER = ClasspathResourceLoader.class.getName();
114    
115        /** Constant for the default profile. */
116        private static final String DEFAULT_PROFILE = "default";
117    
118        /**
119         * Log level events are logged at by default.
120         * @see #getDefaultLogLevel()
121         */
122        private static final Level DEFAULT_LOG_LEVEL = Level.WARNING;
123    
124        /** Default log level. */
125        private static volatile Level defaultLogLevel;
126    
127        /** The modules of the instance. */
128        private Modules modules;
129    
130        /** {@code VelocityEngine} of the generator. */
131        private VelocityEngine velocityEngine;
132    
133        /** The encoding to use for reading templates. */
134        private String templateEncoding;
135    
136        /** The encoding to use for reading files. */
137        private String inputEncoding;
138    
139        /** The encoding to use for writing files. */
140        private String outputEncoding;
141    
142        /** The profile of the instance. */
143        private String profile;
144    
145        /** The listeners of the instance. */
146        private List<Listener> listeners;
147    
148        /** Log level of the instance. */
149        private Level logLevel;
150    
151        /** Cached templates. */
152        private final Map<String, Template> templateCache = new HashMap<String, Template>();
153    
154        /** Creates a new {@code JomcTool} instance. */
155        public JomcTool()
156        {
157            super();
158        }
159    
160        /**
161         * Creates a new {@code JomcTool} instance taking a {@code JomcTool} instance to initialize the new instance with.
162         *
163         * @param tool The instance to initialize the new instance with.
164         */
165        public JomcTool( final JomcTool tool )
166        {
167            this();
168            if ( tool != null )
169            {
170                try
171                {
172                    this.setTemplateEncoding( tool.getTemplateEncoding() );
173                    this.setInputEncoding( tool.getInputEncoding() );
174                    this.setOutputEncoding( tool.getOutputEncoding() );
175                    this.setModules( tool.getModules() );
176                    this.setProfile( tool.getProfile() );
177                    this.setVelocityEngine( tool.getVelocityEngine() );
178                    this.setLogLevel( tool.getLogLevel() );
179                    this.getListeners().addAll( tool.getListeners() );
180                }
181                catch ( final Exception e )
182                {
183                    if ( this.isLoggable( Level.SEVERE ) )
184                    {
185                        this.log( Level.SEVERE, e.getMessage(), e );
186                    }
187                }
188            }
189        }
190    
191        /**
192         * Gets the list of registered listeners.
193         * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
194         * to the returned list will be present inside the object. This is why there is no {@code set} method for the
195         * listeners property.</p>
196         *
197         * @return The list of registered listeners.
198         *
199         * @see #log(java.util.logging.Level, java.lang.String, java.lang.Throwable)
200         */
201        public List<Listener> getListeners()
202        {
203            if ( this.listeners == null )
204            {
205                this.listeners = new LinkedList<Listener>();
206            }
207    
208            return this.listeners;
209        }
210    
211        /**
212         * Gets the default log level events are logged at.
213         * <p>The default log level is controlled by system property {@code org.jomc.tools.JomcTool.defaultLogLevel} holding
214         * the log level to log events at by default. If that property is not set, the {@code WARNING} default is
215         * returned.</p>
216         *
217         * @return The log level events are logged at by default.
218         *
219         * @see #getLogLevel()
220         * @see Level#parse(java.lang.String)
221         */
222        public static Level getDefaultLogLevel()
223        {
224            if ( defaultLogLevel == null )
225            {
226                defaultLogLevel = Level.parse( System.getProperty( "org.jomc.tools.JomcTool.defaultLogLevel",
227                                                                   DEFAULT_LOG_LEVEL.getName() ) );
228    
229            }
230    
231            return defaultLogLevel;
232        }
233    
234        /**
235         * Sets the default log level events are logged at.
236         *
237         * @param value The new default level events are logged at or {@code null}.
238         *
239         * @see #getDefaultLogLevel()
240         */
241        public static void setDefaultLogLevel( final Level value )
242        {
243            defaultLogLevel = value;
244        }
245    
246        /**
247         * Gets the log level of the instance.
248         *
249         * @return The log level of the instance.
250         *
251         * @see #getDefaultLogLevel()
252         * @see #setLogLevel(java.util.logging.Level)
253         * @see #isLoggable(java.util.logging.Level)
254         */
255        public Level getLogLevel()
256        {
257            if ( this.logLevel == null )
258            {
259                this.logLevel = getDefaultLogLevel();
260                this.log( Level.CONFIG, this.getMessage( "defaultLogLevelInfo", new Object[]
261                    {
262                        this.getClass().getCanonicalName(), this.logLevel.getLocalizedName()
263                    } ), null );
264    
265            }
266    
267            return this.logLevel;
268        }
269    
270        /**
271         * Sets the log level of the instance.
272         *
273         * @param value The new log level of the instance or {@code null}.
274         *
275         * @see #getLogLevel()
276         * @see #isLoggable(java.util.logging.Level)
277         */
278        public void setLogLevel( final Level value )
279        {
280            this.logLevel = value;
281        }
282    
283        /**
284         * Checks if a message at a given level is provided to the listeners of the instance.
285         *
286         * @param level The level to test.
287         *
288         * @return {@code true} if messages at {@code level} are provided to the listeners of the instance;
289         * {@code false} if messages at {@code level} are not provided to the listeners of the instance.
290         *
291         * @throws NullPointerException if {@code level} is {@code null}.
292         *
293         * @see #getLogLevel()
294         * @see #setLogLevel(java.util.logging.Level)
295         * @see #log(java.util.logging.Level, java.lang.String, java.lang.Throwable)
296         */
297        public boolean isLoggable( final Level level )
298        {
299            if ( level == null )
300            {
301                throw new NullPointerException( "level" );
302            }
303    
304            return level.intValue() >= this.getLogLevel().intValue();
305        }
306    
307        /**
308         * Gets the Java package name of a specification.
309         *
310         * @param specification The specification to get the Java package name of.
311         *
312         * @return The Java package name of {@code specification}.
313         *
314         * @throws NullPointerException if {@code specification} is {@code null}.
315         */
316        public String getJavaPackageName( final Specification specification )
317        {
318            if ( specification == null )
319            {
320                throw new NullPointerException( "specification" );
321            }
322    
323            return this.getJavaPackageName( specification.getClazz() );
324        }
325    
326        /**
327         * Gets the Java type name of a specification.
328         *
329         * @param specification The specification to get the Java type name of.
330         * @param qualified {@code true} to return the fully qualified type name (with package name prepended);
331         * {@code false} to return the short type name (without package name prepended).
332         *
333         * @return The Java type name of {@code specification}.
334         *
335         * @throws NullPointerException if {@code specification} is {@code null}.
336         */
337        public String getJavaTypeName( final Specification specification, final boolean qualified )
338        {
339            if ( specification == null )
340            {
341                throw new NullPointerException( "specification" );
342            }
343    
344            final StringBuilder typeName = new StringBuilder();
345            final String javaPackageName = this.getJavaPackageName( specification );
346    
347            if ( qualified && javaPackageName.length() > 0 )
348            {
349                typeName.append( javaPackageName ).append( '.' );
350            }
351    
352            typeName.append( javaPackageName.length() > 0
353                             ? specification.getClazz().substring( javaPackageName.length() + 1 )
354                             : specification.getClazz() );
355    
356            return typeName.toString();
357        }
358    
359        /**
360         * Gets the Java class path location of a specification.
361         *
362         * @param specification The specification to return the Java class path location of.
363         *
364         * @return the Java class path location of {@code specification}.
365         *
366         * @throws NullPointerException if {@code specification} is {@code null}.
367         */
368        public String getJavaClasspathLocation( final Specification specification )
369        {
370            if ( specification == null )
371            {
372                throw new NullPointerException( "specification" );
373            }
374    
375            return ( this.getJavaTypeName( specification, true ) ).replace( '.', '/' );
376        }
377    
378        /**
379         * Gets the Java package name of a specification reference.
380         *
381         * @param reference The specification reference to get the Java package name of.
382         *
383         * @return The Java package name of {@code reference}.
384         *
385         * @throws NullPointerException if {@code reference} is {@code null}.
386         */
387        public String getJavaPackageName( final SpecificationReference reference )
388        {
389            if ( reference == null )
390            {
391                throw new NullPointerException( "reference" );
392            }
393    
394            final Specification s = this.getModules().getSpecification( reference.getIdentifier() );
395            assert s != null : "Specification '" + reference.getIdentifier() + "' not found.";
396            return this.getJavaPackageName( s );
397        }
398    
399        /**
400         * Gets the name of a Java type of a given specification reference.
401         *
402         * @param reference The specification reference to get a Java type name of.
403         * @param qualified {@code true} to return the fully qualified type name (with package name prepended);
404         * {@code false} to return the short type name (without package name prepended).
405         *
406         * @return The Java type name of {@code reference}.
407         *
408         * @throws NullPointerException if {@code reference} is {@code null}.
409         */
410        public String getJavaTypeName( final SpecificationReference reference, final boolean qualified )
411        {
412            if ( reference == null )
413            {
414                throw new NullPointerException( "reference" );
415            }
416    
417            final Specification s = this.getModules().getSpecification( reference.getIdentifier() );
418            assert s != null : "Specification '" + reference.getIdentifier() + "' not found.";
419            return this.getJavaTypeName( s, qualified );
420        }
421    
422        /**
423         * Gets the Java package name of an implementation.
424         *
425         * @param implementation The implementation to get the Java package name of.
426         *
427         * @return The Java package name of {@code implementation}.
428         *
429         * @throws NullPointerException if {@code implementation} is {@code null}.
430         */
431        public String getJavaPackageName( final Implementation implementation )
432        {
433            if ( implementation == null )
434            {
435                throw new NullPointerException( "implementation" );
436            }
437    
438            return this.getJavaPackageName( implementation.getClazz() );
439        }
440    
441        /**
442         * Gets the Java type name of an implementation.
443         *
444         * @param implementation The implementation to get the Java type name of.
445         * @param qualified {@code true} to return the fully qualified type name (with package name prepended);
446         * {@code false} to return the short type name (without package name prepended).
447         *
448         * @return The Java type name of {@code implementation}.
449         *
450         * @throws NullPointerException if {@code implementation} is {@code null}.
451         */
452        public String getJavaTypeName( final Implementation implementation, final boolean qualified )
453        {
454            if ( implementation == null )
455            {
456                throw new NullPointerException( "implementation" );
457            }
458    
459            final StringBuilder typeName = new StringBuilder();
460            final String javaPackageName = this.getJavaPackageName( implementation );
461    
462            if ( qualified && javaPackageName.length() > 0 )
463            {
464                typeName.append( javaPackageName ).append( '.' );
465            }
466    
467            typeName.append( javaPackageName.length() > 0
468                             ? implementation.getClazz().substring( javaPackageName.length() + 1 )
469                             : implementation.getClazz() );
470    
471            return typeName.toString();
472        }
473    
474        /**
475         * Gets the Java class path location of an implementation.
476         *
477         * @param implementation The implementation to return the Java class path location of.
478         *
479         * @return The Java class path location of {@code implementation}.
480         *
481         * @throws NullPointerException if {@code implementation} is {@code null}.
482         */
483        public String getJavaClasspathLocation( final Implementation implementation )
484        {
485            if ( implementation == null )
486            {
487                throw new NullPointerException( "implementation" );
488            }
489    
490            return ( this.getJavaTypeName( implementation, true ) ).replace( '.', '/' );
491        }
492    
493        /**
494         * Gets all Java interfaces an implementation implements.
495         *
496         * @param implementation The implementation to get all implemented Java interfaces of.
497         * @param qualified {@code true} to return the fully qualified type names (with package name prepended);
498         * {@code false} to return the short type names (without package name prepended).
499         *
500         * @return All interfaces implemented by {@code implementation}.
501         *
502         * @throws NullPointerException if {@code implementation} is {@code null}.
503         */
504        public List<String> getJavaInterfaceNames( final Implementation implementation, final boolean qualified )
505        {
506            if ( implementation == null )
507            {
508                throw new NullPointerException( "implementation" );
509            }
510    
511            final Specifications specs = this.getModules().getSpecifications( implementation.getIdentifier() );
512            final List<String> col = new ArrayList<String>( specs == null ? 0 : specs.getSpecification().size() );
513    
514            if ( specs != null )
515            {
516                for ( Specification s : specs.getSpecification() )
517                {
518                    final String typeName = this.getJavaTypeName( s, qualified );
519                    if ( !col.contains( typeName ) )
520                    {
521                        col.add( typeName );
522                    }
523                }
524            }
525    
526            return col;
527        }
528    
529        /**
530         * Gets the Java type name of an argument.
531         *
532         * @param argument The argument to get the Java type name of.
533         *
534         * @return The Java type name of {@code argument}.
535         *
536         * @throws NullPointerException if {@code argument} is {@code null}.
537         */
538        public String getJavaTypeName( final Argument argument )
539        {
540            if ( argument == null )
541            {
542                throw new NullPointerException( "argument" );
543            }
544    
545            if ( argument.getType() == ArgumentType.DATE || argument.getType() == ArgumentType.TIME )
546            {
547                return "java.util.Date";
548            }
549            else if ( argument.getType() == ArgumentType.NUMBER )
550            {
551                return "java.lang.Number";
552            }
553            else if ( argument.getType() == ArgumentType.TEXT )
554            {
555                return "java.lang.String";
556            }
557            else
558            {
559                throw new IllegalArgumentException( argument.getType().value() );
560            }
561        }
562    
563        /**
564         * Gets the Java type name of a property.
565         *
566         * @param property The property to get the Java type name of.
567         * @param boxify {@code true} to return the name of the Java wrapper class when the type is a Java primitive type;
568         * {@code false} to return the exact binary name (unboxed name) of the Java type.
569         *
570         * @return The Java type name of {@code property}.
571         *
572         * @throws NullPointerException if {@code property} is {@code null}.
573         */
574        public String getJavaTypeName( final Property property, final boolean boxify )
575        {
576            if ( property == null )
577            {
578                throw new NullPointerException( "property" );
579            }
580    
581            if ( property.getType() != null )
582            {
583                final String typeName = property.getType();
584    
585                if ( boxify )
586                {
587                    if ( Boolean.TYPE.getName().equals( typeName ) )
588                    {
589                        return Boolean.class.getName();
590                    }
591                    if ( Byte.TYPE.getName().equals( typeName ) )
592                    {
593                        return Byte.class.getName();
594                    }
595                    if ( Character.TYPE.getName().equals( typeName ) )
596                    {
597                        return Character.class.getName();
598                    }
599                    if ( Double.TYPE.getName().equals( typeName ) )
600                    {
601                        return Double.class.getName();
602                    }
603                    if ( Float.TYPE.getName().equals( typeName ) )
604                    {
605                        return Float.class.getName();
606                    }
607                    if ( Integer.TYPE.getName().equals( typeName ) )
608                    {
609                        return Integer.class.getName();
610                    }
611                    if ( Long.TYPE.getName().equals( typeName ) )
612                    {
613                        return Long.class.getName();
614                    }
615                    if ( Short.TYPE.getName().equals( typeName ) )
616                    {
617                        return Short.class.getName();
618                    }
619                }
620    
621                return typeName;
622            }
623    
624            return property.getAny() != null ? Object.class.getName() : String.class.getName();
625        }
626    
627        /**
628         * Gets a flag indicating if the type of a given property is a Java primitive.
629         *
630         * @param property The property to query.
631         *
632         * @return {@code true} if the type of {@code property} is a Java primitive; {@code false} if not.
633         *
634         * @throws NullPointerException if {@code property} is {@code null}.
635         */
636        public boolean isJavaPrimitiveType( final Property property )
637        {
638            if ( property == null )
639            {
640                throw new NullPointerException( "property" );
641            }
642    
643            return !this.getJavaTypeName( property, false ).equals( this.getJavaTypeName( property, true ) );
644        }
645    
646        /**
647         * Gets the name of a Java accessor method of a given property.
648         *
649         * @param property The property to get a Java accessor method name of.
650         *
651         * @return The Java accessor method name of {@code property}.
652         *
653         * @throws NullPointerException if {@code property} is {@code null}.
654         */
655        public String getJavaGetterMethodName( final Property property )
656        {
657            if ( property == null )
658            {
659                throw new NullPointerException( "property" );
660            }
661    
662            final char[] name = property.getName().toCharArray();
663            name[0] = Character.toUpperCase( name[0] );
664            String prefix = "get";
665    
666            final String javaTypeName = this.getJavaTypeName( property, true );
667            if ( Boolean.class.getName().equals( javaTypeName ) )
668            {
669                prefix = "is";
670            }
671    
672            return prefix + String.valueOf( name );
673        }
674    
675        /**
676         * Gets the name of a Java type of a given dependency.
677         *
678         * @param dependency The dependency to get a dependency Java type name of.
679         *
680         * @return The Java type name of {@code dependency}.
681         *
682         * @throws NullPointerException if {@code dependency} is {@code null}.
683         */
684        public String getJavaTypeName( final Dependency dependency )
685        {
686            if ( dependency == null )
687            {
688                throw new NullPointerException( "dependency" );
689            }
690    
691            final StringBuilder typeName = new StringBuilder();
692            typeName.append( this.getJavaTypeName( (SpecificationReference) dependency, true ) );
693    
694            final Specification s = this.getModules().getSpecification( dependency.getIdentifier() );
695            if ( s != null && s.getMultiplicity() == Multiplicity.MANY && dependency.getImplementationName() == null )
696            {
697                typeName.append( "[]" );
698            }
699    
700            return typeName.toString();
701        }
702    
703        /**
704         * Gets the name of a Java accessor method of a given dependency.
705         *
706         * @param dependency The dependency to get a Java accessor method name of.
707         *
708         * @return The Java accessor method name of {@code dependency}.
709         *
710         * @throws NullPointerException if {@code dependency} is {@code null}.
711         */
712        public String getJavaGetterMethodName( final Dependency dependency )
713        {
714            if ( dependency == null )
715            {
716                throw new NullPointerException( "dependency" );
717            }
718    
719            final char[] name = dependency.getName().toCharArray();
720            name[0] = Character.toUpperCase( name[0] );
721            return "get" + String.valueOf( name );
722        }
723    
724        /**
725         * Gets the name of a Java accessor method of a given message.
726         *
727         * @param message The message to get a Java accessor method name of.
728         *
729         * @return The Java accessor method name of {@code message}.
730         *
731         * @throws NullPointerException if {@code message} is {@code null}.
732         */
733        public String getJavaGetterMethodName( final Message message )
734        {
735            if ( message == null )
736            {
737                throw new NullPointerException( "message" );
738            }
739    
740            final char[] name = message.getName().toCharArray();
741            name[0] = Character.toUpperCase( name[0] );
742            return "get" + String.valueOf( name ) + "Message";
743        }
744    
745        /**
746         * Gets the name of a Java modifier of a dependency of a given implementation.
747         *
748         * @param implementation The implementation to get a dependency Java modifier name of.
749         * @param dependency The dependency to get a Java modifier name of.
750         *
751         * @return The Java modifier name of {@code dependency} of {@code implementation}.
752         *
753         * @throws NullPointerException if {@code implementation} or {@code dependency} is {@code null}.
754         */
755        public String getJavaModifierName( final Implementation implementation, final Dependency dependency )
756        {
757            if ( implementation == null )
758            {
759                throw new NullPointerException( "implementation" );
760            }
761            if ( dependency == null )
762            {
763                throw new NullPointerException( "dependency" );
764            }
765    
766            return "private";
767        }
768    
769        /**
770         * Gets the name of a Java modifier of a message of a given implementation.
771         *
772         * @param implementation The implementation to get a message Java modifier name of.
773         * @param message The message to get a Java modifier name of.
774         *
775         * @return The Java modifier name of {@code message} of {@code implementation}.
776         *
777         * @throws NullPointerException if {@code implementation} or {@code message} is {@code null}.
778         */
779        public String getJavaModifierName( final Implementation implementation, final Message message )
780        {
781            if ( implementation == null )
782            {
783                throw new NullPointerException( "implementation" );
784            }
785            if ( message == null )
786            {
787                throw new NullPointerException( "message" );
788            }
789    
790            return "private";
791        }
792    
793        /**
794         * Gets the name of a Java modifier for a given property of a given implementation.
795         *
796         * @param implementation The implementation declaring {@code property}.
797         * @param property The property to get a Java modifier name for.
798         *
799         * @return The Java modifier name for {@code property} of {@code implementation}.
800         *
801         * @throws NullPointerException if {@code implementation} or {@code property} is {@code null}.
802         */
803        public String getJavaModifierName( final Implementation implementation, final Property property )
804        {
805            if ( implementation == null )
806            {
807                throw new NullPointerException( "implementation" );
808            }
809            if ( property == null )
810            {
811                throw new NullPointerException( "property" );
812            }
813    
814            String modifier = "private";
815            final Properties specified = this.getModules().getSpecifiedProperties( implementation.getIdentifier() );
816    
817            if ( specified != null && specified.getProperty( property.getName() ) != null )
818            {
819                modifier = "public";
820            }
821    
822            return modifier;
823        }
824    
825        /**
826         * Formats a text to a Javadoc comment.
827         *
828         * @param text The text to format to a Javadoc comment.
829         * @param linebreak The text to replace line breaks with.
830         *
831         * @return {@code text} formatted as a Javadoc comment.
832         *
833         * @throws NullPointerException if {@code text} or {@code linebreak} is {@code null}.
834         */
835        public String getJavadocComment( final Text text, final String linebreak )
836        {
837            if ( text == null )
838            {
839                throw new NullPointerException( "text" );
840            }
841            if ( linebreak == null )
842            {
843                throw new NullPointerException( "linebreak" );
844            }
845    
846            String normalized = text.getValue();
847            normalized = normalized.replaceAll( "\\/\\*\\*", "/*" );
848            normalized = normalized.replaceAll( "\\*/", "/" );
849            normalized = normalized.replaceAll( "\n", "\n" + linebreak );
850            return StringEscapeUtils.escapeHtml( normalized );
851        }
852    
853        /**
854         * Formats a string to a Java string with unicode escapes.
855         *
856         * @param str The string to format to a Java string or {@code null}.
857         *
858         * @return {@code str} formatted as a Java string or {@code null}.
859         */
860        public String getJavaString( final String str )
861        {
862            return StringEscapeUtils.escapeJava( str );
863        }
864    
865        /**
866         * Gets a flag indicating if a given specification declares a Java class.
867         *
868         * @param specification The specification to test.
869         *
870         * @return {@code true} if {@code specification} is declaring the Java class with name
871         * {@code specification.getClazz()}; {@code false} if {@code specification} does not declare that class.
872         *
873         * @throws NullPointerException if {@code specification} is {@code null}.
874         */
875        public boolean isJavaClassDeclaration( final Specification specification )
876        {
877            if ( specification == null )
878            {
879                throw new NullPointerException( "specification" );
880            }
881    
882            return specification.getClazz() != null && specification.getClazz().equals( specification.getIdentifier() );
883        }
884    
885        /**
886         * Gets a flag indicating if a given implementation declares a Java class.
887         *
888         * @param implementation The implementation to test.
889         *
890         * @return {@code true} if {@code implementation} is declaring the Java class with name
891         * {@code implementation.getClazz()}; {@code false} if {@code implementation.getClazz()} is {@code null} or
892         * {@code implementation} does not declare that class.
893         *
894         * @throws NullPointerException if {@code implementation} is {@code null}.
895         */
896        public boolean isJavaClassDeclaration( final Implementation implementation )
897        {
898            if ( implementation == null )
899            {
900                throw new NullPointerException( "implementation" );
901            }
902    
903            return implementation.getClazz() != null && implementation.getClazz().equals( implementation.getIdentifier() );
904        }
905    
906        /**
907         * Gets a flag indicating if the class of a given specification is located in the Java default package.
908         *
909         * @param specification The specification to test.
910         *
911         * @return {@code true} if the class of {@code specification} is located in the Java default package; {@code false}
912         * if not.
913         *
914         * @throws NullPointerException if {@code specification} is {@code null}.
915         */
916        public boolean isJavaDefaultPackage( final Specification specification )
917        {
918            if ( specification == null )
919            {
920                throw new NullPointerException( "specification" );
921            }
922    
923            return this.getJavaPackageName( specification ).length() == 0;
924        }
925    
926        /**
927         * Gets a flag indicating if the class of a given implementation is located in the Java default package.
928         *
929         * @param implementation The implementation to test.
930         *
931         * @return {@code true} if the class of {@code implementation} is located in the Java default package; {@code false}
932         * if not.
933         *
934         * @throws NullPointerException if {@code implementation} is {@code null}.
935         */
936        public boolean isJavaDefaultPackage( final Implementation implementation )
937        {
938            if ( implementation == null )
939            {
940                throw new NullPointerException( "implementation" );
941            }
942    
943            return this.getJavaPackageName( implementation ).length() == 0;
944        }
945    
946        /**
947         * Gets the display language of a given language code.
948         *
949         * @param language The language code to get the display language of.
950         *
951         * @return The display language of {@code language}.
952         *
953         * @throws NullPointerException if {@code language} is {@code null}.
954         */
955        public String getDisplayLanguage( final String language )
956        {
957            if ( language == null )
958            {
959                throw new NullPointerException( "language" );
960            }
961    
962            final Locale locale = new Locale( language );
963            return locale.getDisplayLanguage( locale );
964        }
965    
966        /**
967         * Formats a calendar instance to a string.
968         *
969         * @param calendar The calendar to format.
970         *
971         * @return Date of {@code calendar} formatted using a short format style pattern.
972         *
973         * @throws NullPointerException if {@code calendar} is {@code null}.
974         *
975         * @see DateFormat#SHORT
976         */
977        public String getShortDate( final Calendar calendar )
978        {
979            if ( calendar == null )
980            {
981                throw new NullPointerException( "calendar" );
982            }
983    
984            return DateFormat.getDateInstance( DateFormat.SHORT ).format( calendar.getTime() );
985        }
986    
987        /**
988         * Formats a calendar instance to a string.
989         *
990         * @param calendar The calendar to format.
991         *
992         * @return Date of {@code calendar} formatted using a long format style pattern.
993         *
994         * @throws NullPointerException if {@code calendar} is {@code null}.
995         *
996         * @see DateFormat#LONG
997         */
998        public String getLongDate( final Calendar calendar )
999        {
1000            if ( calendar == null )
1001            {
1002                throw new NullPointerException( "calendar" );
1003            }
1004    
1005            return DateFormat.getDateInstance( DateFormat.LONG ).format( calendar.getTime() );
1006        }
1007    
1008        /**
1009         * Formats a calendar instance to a string.
1010         *
1011         * @param calendar The calendar to format.
1012         *
1013         * @return Time of {@code calendar} formatted using a short format style pattern.
1014         *
1015         * @throws NullPointerException if {@code calendar} is {@code null}.
1016         *
1017         * @see DateFormat#SHORT
1018         */
1019        public String getShortTime( final Calendar calendar )
1020        {
1021            if ( calendar == null )
1022            {
1023                throw new NullPointerException( "calendar" );
1024            }
1025    
1026            return DateFormat.getTimeInstance( DateFormat.SHORT ).format( calendar.getTime() );
1027        }
1028    
1029        /**
1030         * Formats a calendar instance to a string.
1031         *
1032         * @param calendar The calendar to format.
1033         *
1034         * @return Time of {@code calendar} formatted using a long format style pattern.
1035         *
1036         * @throws NullPointerException if {@code calendar} is {@code null}.
1037         *
1038         * @see DateFormat#LONG
1039         */
1040        public String getLongTime( final Calendar calendar )
1041        {
1042            if ( calendar == null )
1043            {
1044                throw new NullPointerException( "calendar" );
1045            }
1046    
1047            return DateFormat.getTimeInstance( DateFormat.LONG ).format( calendar.getTime() );
1048        }
1049    
1050        /**
1051         * Formats a calendar instance to a string.
1052         *
1053         * @param calendar The calendar to format.
1054         *
1055         * @return Date and time of {@code calendar} formatted using a short format style pattern.
1056         *
1057         * @throws NullPointerException if {@code calendar} is {@code null}.
1058         *
1059         * @see DateFormat#SHORT
1060         */
1061        public String getShortDateTime( final Calendar calendar )
1062        {
1063            if ( calendar == null )
1064            {
1065                throw new NullPointerException( "calendar" );
1066            }
1067    
1068            return DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT ).format( calendar.getTime() );
1069        }
1070    
1071        /**
1072         * Formats a calendar instance to a string.
1073         *
1074         * @param calendar The calendar to format.
1075         *
1076         * @return Date and time of {@code calendar} formatted using a long format style pattern.
1077         *
1078         * @throws NullPointerException if {@code calendar} is {@code null}.
1079         *
1080         * @see DateFormat#LONG
1081         */
1082        public String getLongDateTime( final Calendar calendar )
1083        {
1084            if ( calendar == null )
1085            {
1086                throw new NullPointerException( "calendar" );
1087            }
1088    
1089            return DateFormat.getDateTimeInstance( DateFormat.LONG, DateFormat.LONG ).format( calendar.getTime() );
1090        }
1091    
1092        /**
1093         * Gets a string describing the range of years for given calendars.
1094         *
1095         * @param start The start of the range.
1096         * @param end The end of the range.
1097         *
1098         * @return Formatted range of the years of {@code start} and {@code end}.
1099         *
1100         * @throws NullPointerException if {@code start} or {@code end} is {@code null}.
1101         */
1102        public String getYears( final Calendar start, final Calendar end )
1103        {
1104            if ( start == null )
1105            {
1106                throw new NullPointerException( "start" );
1107            }
1108            if ( end == null )
1109            {
1110                throw new NullPointerException( "end" );
1111            }
1112    
1113            final Format yearFormat = new SimpleDateFormat( "yyyy" );
1114            final int s = start.get( Calendar.YEAR );
1115            final int e = end.get( Calendar.YEAR );
1116            final StringBuilder years = new StringBuilder();
1117    
1118            if ( s != e )
1119            {
1120                if ( s < e )
1121                {
1122                    years.append( yearFormat.format( start.getTime() ) ).append( " - " ).
1123                        append( yearFormat.format( end.getTime() ) );
1124    
1125                }
1126                else
1127                {
1128                    years.append( yearFormat.format( end.getTime() ) ).append( " - " ).
1129                        append( yearFormat.format( start.getTime() ) );
1130    
1131                }
1132            }
1133            else
1134            {
1135                years.append( yearFormat.format( start.getTime() ) );
1136            }
1137    
1138            return years.toString();
1139        }
1140    
1141        /**
1142         * Gets the modules of the instance.
1143         *
1144         * @return The modules of the instance.
1145         *
1146         * @see #setModules(org.jomc.model.Modules)
1147         */
1148        public Modules getModules()
1149        {
1150            if ( this.modules == null )
1151            {
1152                this.modules = new Modules();
1153            }
1154    
1155            return this.modules;
1156        }
1157    
1158        /**
1159         * Sets the modules of the instance.
1160         *
1161         * @param value The new modules of the instance.
1162         *
1163         * @see #getModules()
1164         */
1165        public void setModules( final Modules value )
1166        {
1167            this.modules = value;
1168        }
1169    
1170        /**
1171         * Gets the {@code VelocityEngine} used for generating source code.
1172         *
1173         * @return The {@code VelocityEngine} used for generating source code.
1174         *
1175         * @throws IllegalStateException if initializing a new velocity engine fails.
1176         *
1177         * @see #setVelocityEngine(org.apache.velocity.app.VelocityEngine)
1178         */
1179        public VelocityEngine getVelocityEngine()
1180        {
1181            if ( this.velocityEngine == null )
1182            {
1183                try
1184                {
1185                    final java.util.Properties props = new java.util.Properties();
1186                    props.put( "resource.loader", "class" );
1187                    props.put( "class.resource.loader.class", VELOCITY_RESOURCE_LOADER );
1188                    props.put( "runtime.references.strict", Boolean.TRUE.toString() );
1189    
1190                    final VelocityEngine engine = new VelocityEngine();
1191                    engine.setProperty( RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new LogChute()
1192                    {
1193    
1194                        public void init( final RuntimeServices runtimeServices ) throws Exception
1195                        {
1196                        }
1197    
1198                        public void log( final int level, final String message )
1199                        {
1200                            this.log( level, message, null );
1201                        }
1202    
1203                        public void log( final int level, final String message, final Throwable throwable )
1204                        {
1205                            JomcTool.this.log( this.toToolLevel( level ), message, throwable );
1206                        }
1207    
1208                        public boolean isLevelEnabled( final int level )
1209                        {
1210                            return isLoggable( this.toToolLevel( level ) );
1211                        }
1212    
1213                        private Level toToolLevel( final int logChuteLevel )
1214                        {
1215                            switch ( logChuteLevel )
1216                            {
1217                                case LogChute.DEBUG_ID:
1218                                    return Level.FINE;
1219    
1220                                case LogChute.ERROR_ID:
1221                                    return Level.SEVERE;
1222    
1223                                case LogChute.INFO_ID:
1224                                    return Level.INFO;
1225    
1226                                case LogChute.TRACE_ID:
1227                                    return Level.FINER;
1228    
1229                                case LogChute.WARN_ID:
1230                                    return Level.WARNING;
1231    
1232                                default:
1233                                    return Level.FINEST;
1234    
1235                            }
1236                        }
1237    
1238                    } );
1239    
1240                    engine.init( props );
1241                    this.velocityEngine = engine;
1242                }
1243                catch ( final Exception e )
1244                {
1245                    throw new IllegalStateException( e );
1246                }
1247            }
1248    
1249            return this.velocityEngine;
1250        }
1251    
1252        /**
1253         * Sets the {@code VelocityEngine} of the instance.
1254         *
1255         * @param value The new {@code VelocityEngine} of the instance.
1256         *
1257         * @see #getVelocityEngine()
1258         */
1259        public void setVelocityEngine( final VelocityEngine value )
1260        {
1261            this.velocityEngine = value;
1262        }
1263    
1264        /**
1265         * Gets the velocity context used for merging templates.
1266         *
1267         * @return The velocity context used for merging templates.
1268         */
1269        public VelocityContext getVelocityContext()
1270        {
1271            final Date now = new Date();
1272            final VelocityContext ctx = new VelocityContext();
1273            ctx.put( "modules", this.getModules() );
1274            ctx.put( "tool", this );
1275            ctx.put( "calendar", Calendar.getInstance() );
1276            ctx.put( "now", new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ" ).format( now ) );
1277            ctx.put( "year", new SimpleDateFormat( "yyyy" ).format( now ) );
1278            ctx.put( "month", new SimpleDateFormat( "MM" ).format( now ) );
1279            ctx.put( "day", new SimpleDateFormat( "dd" ).format( now ) );
1280            ctx.put( "hour", new SimpleDateFormat( "HH" ).format( now ) );
1281            ctx.put( "minute", new SimpleDateFormat( "mm" ).format( now ) );
1282            ctx.put( "second", new SimpleDateFormat( "ss" ).format( now ) );
1283            ctx.put( "timezone", new SimpleDateFormat( "Z" ).format( now ) );
1284            return ctx;
1285        }
1286    
1287        /**
1288         * Gets the encoding to use for reading templates.
1289         *
1290         * @return The encoding to use for reading templates.
1291         *
1292         * @see #setTemplateEncoding(java.lang.String)
1293         */
1294        public String getTemplateEncoding()
1295        {
1296            if ( this.templateEncoding == null )
1297            {
1298                this.templateEncoding = this.getMessage( "buildSourceEncoding", null );
1299            }
1300    
1301            return this.templateEncoding;
1302        }
1303    
1304        /**
1305         * Sets the encoding to use for reading templates.
1306         *
1307         * @param value The encoding to use for reading templates.
1308         *
1309         * @see #getTemplateEncoding()
1310         */
1311        public void setTemplateEncoding( final String value )
1312        {
1313            this.templateEncoding = value;
1314            this.templateCache.clear();
1315        }
1316    
1317        /**
1318         * Gets the encoding to use for reading files.
1319         *
1320         * @return The encoding to use for reading files.
1321         *
1322         * @see #setInputEncoding(java.lang.String)
1323         */
1324        public String getInputEncoding()
1325        {
1326            if ( this.inputEncoding == null )
1327            {
1328                this.inputEncoding = new InputStreamReader( new ByteArrayInputStream( NO_BYTES ) ).getEncoding();
1329                if ( this.isLoggable( Level.FINE ) )
1330                {
1331                    this.log( Level.FINE, this.getMessage( "defaultInputEncoding", new Object[]
1332                        {
1333                            this.inputEncoding
1334                        } ), null );
1335    
1336                }
1337            }
1338    
1339            return this.inputEncoding;
1340        }
1341    
1342        /**
1343         * Sets the encoding to use for reading files.
1344         *
1345         * @param value The encoding to use for reading files.
1346         *
1347         * @see #getInputEncoding()
1348         */
1349        public void setInputEncoding( final String value )
1350        {
1351            this.inputEncoding = value;
1352        }
1353    
1354        /**
1355         * Gets the encoding to use for writing files.
1356         *
1357         * @return The encoding to use for writing files.
1358         *
1359         * @see #setOutputEncoding(java.lang.String)
1360         */
1361        public String getOutputEncoding()
1362        {
1363            if ( this.outputEncoding == null )
1364            {
1365                this.outputEncoding = new OutputStreamWriter( new ByteArrayOutputStream() ).getEncoding();
1366                if ( this.isLoggable( Level.FINE ) )
1367                {
1368                    this.log( Level.FINE, this.getMessage( "defaultOutputEncoding", new Object[]
1369                        {
1370                            this.outputEncoding
1371                        } ), null );
1372    
1373                }
1374            }
1375    
1376            return this.outputEncoding;
1377        }
1378    
1379        /**
1380         * Sets the encoding to use for writing files.
1381         *
1382         * @param value The encoding to use for writing files.
1383         *
1384         * @see #getOutputEncoding()
1385         */
1386        public void setOutputEncoding( final String value )
1387        {
1388            this.outputEncoding = value;
1389        }
1390    
1391        /**
1392         * Gets the profile of the instance.
1393         *
1394         * @return The profile of the instance.
1395         *
1396         * @see #setProfile(java.lang.String)
1397         */
1398        public String getProfile()
1399        {
1400            if ( this.profile == null )
1401            {
1402                this.profile = DEFAULT_PROFILE;
1403                if ( this.isLoggable( Level.FINE ) )
1404                {
1405                    this.log( Level.FINE, this.getMessage( "defaultProfile", new Object[]
1406                        {
1407                            this.profile
1408                        } ), null );
1409    
1410                }
1411            }
1412    
1413            return this.profile;
1414        }
1415    
1416        /**
1417         * Sets the profile of the instance.
1418         *
1419         * @param value The profile of the instance.
1420         *
1421         * @see #getProfile()
1422         */
1423        public void setProfile( final String value )
1424        {
1425            this.profile = value;
1426            this.templateCache.clear();
1427        }
1428    
1429        /**
1430         * Gets a velocity template for a given name.
1431         * <p>This method returns the template corresponding to the profile of the instance. If that template is not found,
1432         * the template of the default profile is returned so that only templates differing from the default templates need
1433         * to be provided when exchanging templates.</p>
1434         *
1435         * @param templateName The name of the template to get.
1436         *
1437         * @return The template matching {@code templateName}.
1438         *
1439         * @throws NullPointerException if {@code templateName} is {@code null}.
1440         * @throws IOException if getting the template fails.
1441         *
1442         * @see #getProfile()
1443         * @see #getTemplateEncoding()
1444         */
1445        public Template getVelocityTemplate( final String templateName ) throws IOException
1446        {
1447            if ( templateName == null )
1448            {
1449                throw new NullPointerException( "templateName" );
1450            }
1451    
1452            Template template = this.templateCache.get( templateName );
1453    
1454            if ( template == null )
1455            {
1456                try
1457                {
1458                    template = this.getVelocityEngine().getTemplate(
1459                        TEMPLATE_PREFIX + this.getProfile() + "/" + templateName, this.getTemplateEncoding() );
1460    
1461                    this.templateCache.put( templateName, template );
1462                }
1463                catch ( final ResourceNotFoundException e )
1464                {
1465                    if ( this.isLoggable( Level.CONFIG ) )
1466                    {
1467                        this.log( Level.CONFIG, this.getMessage( "templateNotFound", new Object[]
1468                            {
1469                                templateName, this.getProfile()
1470                            } ), e );
1471    
1472                    }
1473    
1474                    try
1475                    {
1476                        template = this.getVelocityEngine().getTemplate(
1477                            TEMPLATE_PREFIX + DEFAULT_PROFILE + "/" + templateName, this.getTemplateEncoding() );
1478    
1479                        if ( this.isLoggable( Level.CONFIG ) )
1480                        {
1481                            this.log( Level.CONFIG, this.getMessage( "defaultTemplate", new Object[]
1482                                {
1483                                    templateName, DEFAULT_PROFILE
1484                                } ), e );
1485    
1486                        }
1487    
1488                        this.templateCache.put( templateName, template );
1489                    }
1490                    catch ( final ResourceNotFoundException e2 )
1491                    {
1492                        throw (IOException) new IOException( this.getMessage( "templateNotFound", new Object[]
1493                            {
1494                                templateName, DEFAULT_PROFILE
1495                            } ) ).initCause( e2 );
1496    
1497                    }
1498                    catch ( final Exception e2 )
1499                    {
1500                        throw (IOException) new IOException( this.getMessage( "failedGettingTemplate", new Object[]
1501                            {
1502                                templateName
1503                            } ) ).initCause( e2 );
1504    
1505                    }
1506                }
1507                catch ( final Exception e )
1508                {
1509                    throw (IOException) new IOException( this.getMessage( "failedGettingTemplate", new Object[]
1510                        {
1511                            templateName
1512                        } ) ).initCause( e );
1513    
1514                }
1515            }
1516    
1517            return template;
1518        }
1519    
1520        /**
1521         * Notifies registered listeners.
1522         *
1523         * @param level The level of the event.
1524         * @param message The message of the event or {@code null}.
1525         * @param throwable The throwable of the event or {@code null}.
1526         *
1527         * @throws NullPointerException if {@code level} is {@code null}.
1528         *
1529         * @see #getListeners()
1530         */
1531        protected void log( final Level level, final String message, final Throwable throwable )
1532        {
1533            if ( level == null )
1534            {
1535                throw new NullPointerException( "level" );
1536            }
1537    
1538            if ( this.isLoggable( level ) )
1539            {
1540                for ( Listener l : this.getListeners() )
1541                {
1542                    l.onLog( level, message, throwable );
1543                }
1544            }
1545        }
1546    
1547        private String getJavaPackageName( final String identifier )
1548        {
1549            if ( identifier == null )
1550            {
1551                throw new NullPointerException( "identifier" );
1552            }
1553    
1554            final int idx = identifier.lastIndexOf( '.' );
1555            return idx != -1 ? identifier.substring( 0, idx ) : "";
1556        }
1557    
1558        private String getMessage( final String key, final Object args )
1559        {
1560            if ( key == null )
1561            {
1562                throw new NullPointerException( "key" );
1563            }
1564    
1565            final ResourceBundle b = ResourceBundle.getBundle( JomcTool.class.getName().replace( '.', '/' ) );
1566            return args == null ? b.getString( key ) : new MessageFormat( b.getString( key ) ).format( args );
1567        }
1568    
1569    }