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 891 2009-11-02 03:40:00Z 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 891 2009-11-02 03:40:00Z 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.getAny() != null )
582            {
583                return Object.class.getName();
584            }
585            if ( property.getType() != null )
586            {
587                final String typeName = property.getType();
588    
589                if ( boxify )
590                {
591                    if ( Boolean.TYPE.getName().equals( typeName ) )
592                    {
593                        return Boolean.class.getName();
594                    }
595                    if ( Byte.TYPE.getName().equals( typeName ) )
596                    {
597                        return Byte.class.getName();
598                    }
599                    if ( Character.TYPE.getName().equals( typeName ) )
600                    {
601                        return Character.class.getName();
602                    }
603                    if ( Double.TYPE.getName().equals( typeName ) )
604                    {
605                        return Double.class.getName();
606                    }
607                    if ( Float.TYPE.getName().equals( typeName ) )
608                    {
609                        return Float.class.getName();
610                    }
611                    if ( Integer.TYPE.getName().equals( typeName ) )
612                    {
613                        return Integer.class.getName();
614                    }
615                    if ( Long.TYPE.getName().equals( typeName ) )
616                    {
617                        return Long.class.getName();
618                    }
619                    if ( Short.TYPE.getName().equals( typeName ) )
620                    {
621                        return Short.class.getName();
622                    }
623                }
624    
625                return typeName;
626            }
627    
628            return String.class.getName();
629        }
630    
631        /**
632         * Gets a flag indicating if the type of a given property is a Java primitive.
633         *
634         * @param property The property to query.
635         *
636         * @return {@code true} if the type of {@code property} is a Java primitive; {@code false} if not.
637         *
638         * @throws NullPointerException if {@code property} is {@code null}.
639         */
640        public boolean isJavaPrimitiveType( final Property property )
641        {
642            if ( property == null )
643            {
644                throw new NullPointerException( "property" );
645            }
646    
647            return !this.getJavaTypeName( property, false ).equals( this.getJavaTypeName( property, true ) );
648        }
649    
650        /**
651         * Gets the name of a Java accessor method of a given property.
652         *
653         * @param property The property to get a Java accessor method name of.
654         *
655         * @return The Java accessor method name of {@code property}.
656         *
657         * @throws NullPointerException if {@code property} is {@code null}.
658         */
659        public String getJavaGetterMethodName( final Property property )
660        {
661            if ( property == null )
662            {
663                throw new NullPointerException( "property" );
664            }
665    
666            final char[] name = property.getName().toCharArray();
667            name[0] = Character.toUpperCase( name[0] );
668            String prefix = "get";
669    
670            final String javaTypeName = this.getJavaTypeName( property, true );
671            if ( Boolean.class.getName().equals( javaTypeName ) )
672            {
673                prefix = "is";
674            }
675    
676            return prefix + String.valueOf( name );
677        }
678    
679        /**
680         * Gets the name of a Java type of a given dependency.
681         *
682         * @param dependency The dependency to get a dependency Java type name of.
683         *
684         * @return The Java type name of {@code dependency}.
685         *
686         * @throws NullPointerException if {@code dependency} is {@code null}.
687         */
688        public String getJavaTypeName( final Dependency dependency )
689        {
690            if ( dependency == null )
691            {
692                throw new NullPointerException( "dependency" );
693            }
694    
695            final StringBuilder typeName = new StringBuilder();
696            typeName.append( this.getJavaTypeName( (SpecificationReference) dependency, true ) );
697    
698            final Specification s = this.getModules().getSpecification( dependency.getIdentifier() );
699            if ( s != null && s.getMultiplicity() == Multiplicity.MANY && dependency.getImplementationName() == null )
700            {
701                typeName.append( "[]" );
702            }
703    
704            return typeName.toString();
705        }
706    
707        /**
708         * Gets the name of a Java accessor method of a given dependency.
709         *
710         * @param dependency The dependency to get a Java accessor method name of.
711         *
712         * @return The Java accessor method name of {@code dependency}.
713         *
714         * @throws NullPointerException if {@code dependency} is {@code null}.
715         */
716        public String getJavaGetterMethodName( final Dependency dependency )
717        {
718            if ( dependency == null )
719            {
720                throw new NullPointerException( "dependency" );
721            }
722    
723            final char[] name = dependency.getName().toCharArray();
724            name[0] = Character.toUpperCase( name[0] );
725            return "get" + String.valueOf( name );
726        }
727    
728        /**
729         * Gets the name of a Java accessor method of a given message.
730         *
731         * @param message The message to get a Java accessor method name of.
732         *
733         * @return The Java accessor method name of {@code message}.
734         *
735         * @throws NullPointerException if {@code message} is {@code null}.
736         */
737        public String getJavaGetterMethodName( final Message message )
738        {
739            if ( message == null )
740            {
741                throw new NullPointerException( "message" );
742            }
743    
744            final char[] name = message.getName().toCharArray();
745            name[0] = Character.toUpperCase( name[0] );
746            return "get" + String.valueOf( name ) + "Message";
747        }
748    
749        /**
750         * Gets the name of a Java modifier of a dependency of a given implementation.
751         *
752         * @param implementation The implementation to get a dependency Java modifier name of.
753         * @param dependency The dependency to get a Java modifier name of.
754         *
755         * @return The Java modifier name of {@code dependency} of {@code implementation}.
756         *
757         * @throws NullPointerException if {@code implementation} or {@code dependency} is {@code null}.
758         */
759        public String getJavaModifierName( final Implementation implementation, final Dependency dependency )
760        {
761            if ( implementation == null )
762            {
763                throw new NullPointerException( "implementation" );
764            }
765            if ( dependency == null )
766            {
767                throw new NullPointerException( "dependency" );
768            }
769    
770            return "private";
771        }
772    
773        /**
774         * Gets the name of a Java modifier of a message of a given implementation.
775         *
776         * @param implementation The implementation to get a message Java modifier name of.
777         * @param message The message to get a Java modifier name of.
778         *
779         * @return The Java modifier name of {@code message} of {@code implementation}.
780         *
781         * @throws NullPointerException if {@code implementation} or {@code message} is {@code null}.
782         */
783        public String getJavaModifierName( final Implementation implementation, final Message message )
784        {
785            if ( implementation == null )
786            {
787                throw new NullPointerException( "implementation" );
788            }
789            if ( message == null )
790            {
791                throw new NullPointerException( "message" );
792            }
793    
794            return "private";
795        }
796    
797        /**
798         * Gets the name of a Java modifier for a given property of a given implementation.
799         *
800         * @param implementation The implementation declaring {@code property}.
801         * @param property The property to get a Java modifier name for.
802         *
803         * @return The Java modifier name for {@code property} of {@code implementation}.
804         *
805         * @throws NullPointerException if {@code implementation} or {@code property} is {@code null}.
806         */
807        public String getJavaModifierName( final Implementation implementation, final Property property )
808        {
809            if ( implementation == null )
810            {
811                throw new NullPointerException( "implementation" );
812            }
813            if ( property == null )
814            {
815                throw new NullPointerException( "property" );
816            }
817    
818            String modifier = "private";
819            final Properties specified = this.getModules().getSpecifiedProperties( implementation.getIdentifier() );
820    
821            if ( specified != null && specified.getProperty( property.getName() ) != null )
822            {
823                modifier = "public";
824            }
825    
826            return modifier;
827        }
828    
829        /**
830         * Formats a text to a Javadoc comment.
831         *
832         * @param text The text to format to a Javadoc comment.
833         * @param linebreak The text to replace line breaks with.
834         *
835         * @return {@code text} formatted as a Javadoc comment.
836         *
837         * @throws NullPointerException if {@code text} or {@code linebreak} is {@code null}.
838         */
839        public String getJavadocComment( final Text text, final String linebreak )
840        {
841            if ( text == null )
842            {
843                throw new NullPointerException( "text" );
844            }
845            if ( linebreak == null )
846            {
847                throw new NullPointerException( "linebreak" );
848            }
849    
850            String normalized = text.getValue();
851            normalized = normalized.replaceAll( "\\/\\*\\*", "/*" );
852            normalized = normalized.replaceAll( "\\*/", "/" );
853            normalized = normalized.replaceAll( "\n", "\n" + linebreak );
854            return StringEscapeUtils.escapeHtml( normalized );
855        }
856    
857        /**
858         * Formats a string to a Java string with unicode escapes.
859         *
860         * @param str The string to format to a Java string or {@code null}.
861         *
862         * @return {@code str} formatted as a Java string or {@code null}.
863         */
864        public String getJavaString( final String str )
865        {
866            return StringEscapeUtils.escapeJava( str );
867        }
868    
869        /**
870         * Gets a flag indicating if a given specification declares a Java class.
871         *
872         * @param specification The specification to test.
873         *
874         * @return {@code true} if {@code specification} is declaring the Java class with name
875         * {@code specification.getClazz()}; {@code false} if {@code specification} does not declare that class.
876         *
877         * @throws NullPointerException if {@code specification} is {@code null}.
878         */
879        public boolean isJavaClassDeclaration( final Specification specification )
880        {
881            if ( specification == null )
882            {
883                throw new NullPointerException( "specification" );
884            }
885    
886            return specification.getClazz() != null && specification.getClazz().equals( specification.getIdentifier() );
887        }
888    
889        /**
890         * Gets a flag indicating if a given implementation declares a Java class.
891         *
892         * @param implementation The implementation to test.
893         *
894         * @return {@code true} if {@code implementation} is declaring the Java class with name
895         * {@code implementation.getClazz()}; {@code false} if {@code implementation.getClazz()} is {@code null} or
896         * {@code implementation} does not declare that class.
897         *
898         * @throws NullPointerException if {@code implementation} is {@code null}.
899         */
900        public boolean isJavaClassDeclaration( final Implementation implementation )
901        {
902            if ( implementation == null )
903            {
904                throw new NullPointerException( "implementation" );
905            }
906    
907            return implementation.getClazz() != null && implementation.getClazz().equals( implementation.getIdentifier() );
908        }
909    
910        /**
911         * Gets a flag indicating if the class of a given specification is located in the Java default package.
912         *
913         * @param specification The specification to test.
914         *
915         * @return {@code true} if the class of {@code specification} is located in the Java default package; {@code false}
916         * if not.
917         *
918         * @throws NullPointerException if {@code specification} is {@code null}.
919         */
920        public boolean isJavaDefaultPackage( final Specification specification )
921        {
922            if ( specification == null )
923            {
924                throw new NullPointerException( "specification" );
925            }
926    
927            return this.getJavaPackageName( specification ).length() == 0;
928        }
929    
930        /**
931         * Gets a flag indicating if the class of a given implementation is located in the Java default package.
932         *
933         * @param implementation The implementation to test.
934         *
935         * @return {@code true} if the class of {@code implementation} is located in the Java default package; {@code false}
936         * if not.
937         *
938         * @throws NullPointerException if {@code implementation} is {@code null}.
939         */
940        public boolean isJavaDefaultPackage( final Implementation implementation )
941        {
942            if ( implementation == null )
943            {
944                throw new NullPointerException( "implementation" );
945            }
946    
947            return this.getJavaPackageName( implementation ).length() == 0;
948        }
949    
950        /**
951         * Gets the display language of a given language code.
952         *
953         * @param language The language code to get the display language of.
954         *
955         * @return The display language of {@code language}.
956         *
957         * @throws NullPointerException if {@code language} is {@code null}.
958         */
959        public String getDisplayLanguage( final String language )
960        {
961            if ( language == null )
962            {
963                throw new NullPointerException( "language" );
964            }
965    
966            final Locale locale = new Locale( language );
967            return locale.getDisplayLanguage( locale );
968        }
969    
970        /**
971         * Formats a calendar instance to a string.
972         *
973         * @param calendar The calendar to format.
974         *
975         * @return Date of {@code calendar} formatted using a short format style pattern.
976         *
977         * @throws NullPointerException if {@code calendar} is {@code null}.
978         *
979         * @see DateFormat#SHORT
980         */
981        public String getShortDate( final Calendar calendar )
982        {
983            if ( calendar == null )
984            {
985                throw new NullPointerException( "calendar" );
986            }
987    
988            return DateFormat.getDateInstance( DateFormat.SHORT ).format( calendar.getTime() );
989        }
990    
991        /**
992         * Formats a calendar instance to a string.
993         *
994         * @param calendar The calendar to format.
995         *
996         * @return Date of {@code calendar} formatted using a long format style pattern.
997         *
998         * @throws NullPointerException if {@code calendar} is {@code null}.
999         *
1000         * @see DateFormat#LONG
1001         */
1002        public String getLongDate( final Calendar calendar )
1003        {
1004            if ( calendar == null )
1005            {
1006                throw new NullPointerException( "calendar" );
1007            }
1008    
1009            return DateFormat.getDateInstance( DateFormat.LONG ).format( calendar.getTime() );
1010        }
1011    
1012        /**
1013         * Formats a calendar instance to a string.
1014         *
1015         * @param calendar The calendar to format.
1016         *
1017         * @return Time of {@code calendar} formatted using a short format style pattern.
1018         *
1019         * @throws NullPointerException if {@code calendar} is {@code null}.
1020         *
1021         * @see DateFormat#SHORT
1022         */
1023        public String getShortTime( final Calendar calendar )
1024        {
1025            if ( calendar == null )
1026            {
1027                throw new NullPointerException( "calendar" );
1028            }
1029    
1030            return DateFormat.getTimeInstance( DateFormat.SHORT ).format( calendar.getTime() );
1031        }
1032    
1033        /**
1034         * Formats a calendar instance to a string.
1035         *
1036         * @param calendar The calendar to format.
1037         *
1038         * @return Time of {@code calendar} formatted using a long format style pattern.
1039         *
1040         * @throws NullPointerException if {@code calendar} is {@code null}.
1041         *
1042         * @see DateFormat#LONG
1043         */
1044        public String getLongTime( final Calendar calendar )
1045        {
1046            if ( calendar == null )
1047            {
1048                throw new NullPointerException( "calendar" );
1049            }
1050    
1051            return DateFormat.getTimeInstance( DateFormat.LONG ).format( calendar.getTime() );
1052        }
1053    
1054        /**
1055         * Formats a calendar instance to a string.
1056         *
1057         * @param calendar The calendar to format.
1058         *
1059         * @return Date and time of {@code calendar} formatted using a short format style pattern.
1060         *
1061         * @throws NullPointerException if {@code calendar} is {@code null}.
1062         *
1063         * @see DateFormat#SHORT
1064         */
1065        public String getShortDateTime( final Calendar calendar )
1066        {
1067            if ( calendar == null )
1068            {
1069                throw new NullPointerException( "calendar" );
1070            }
1071    
1072            return DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT ).format( calendar.getTime() );
1073        }
1074    
1075        /**
1076         * Formats a calendar instance to a string.
1077         *
1078         * @param calendar The calendar to format.
1079         *
1080         * @return Date and time of {@code calendar} formatted using a long format style pattern.
1081         *
1082         * @throws NullPointerException if {@code calendar} is {@code null}.
1083         *
1084         * @see DateFormat#LONG
1085         */
1086        public String getLongDateTime( final Calendar calendar )
1087        {
1088            if ( calendar == null )
1089            {
1090                throw new NullPointerException( "calendar" );
1091            }
1092    
1093            return DateFormat.getDateTimeInstance( DateFormat.LONG, DateFormat.LONG ).format( calendar.getTime() );
1094        }
1095    
1096        /**
1097         * Gets a string describing the range of years for given calendars.
1098         *
1099         * @param start The start of the range.
1100         * @param end The end of the range.
1101         *
1102         * @return Formatted range of the years of {@code start} and {@code end}.
1103         *
1104         * @throws NullPointerException if {@code start} or {@code end} is {@code null}.
1105         */
1106        public String getYears( final Calendar start, final Calendar end )
1107        {
1108            if ( start == null )
1109            {
1110                throw new NullPointerException( "start" );
1111            }
1112            if ( end == null )
1113            {
1114                throw new NullPointerException( "end" );
1115            }
1116    
1117            final Format yearFormat = new SimpleDateFormat( "yyyy" );
1118            final int s = start.get( Calendar.YEAR );
1119            final int e = end.get( Calendar.YEAR );
1120            final StringBuilder years = new StringBuilder();
1121    
1122            if ( s != e )
1123            {
1124                if ( s < e )
1125                {
1126                    years.append( yearFormat.format( start.getTime() ) ).append( " - " ).
1127                        append( yearFormat.format( end.getTime() ) );
1128    
1129                }
1130                else
1131                {
1132                    years.append( yearFormat.format( end.getTime() ) ).append( " - " ).
1133                        append( yearFormat.format( start.getTime() ) );
1134    
1135                }
1136            }
1137            else
1138            {
1139                years.append( yearFormat.format( start.getTime() ) );
1140            }
1141    
1142            return years.toString();
1143        }
1144    
1145        /**
1146         * Gets the modules of the instance.
1147         *
1148         * @return The modules of the instance.
1149         *
1150         * @see #setModules(org.jomc.model.Modules)
1151         */
1152        public Modules getModules()
1153        {
1154            if ( this.modules == null )
1155            {
1156                this.modules = new Modules();
1157            }
1158    
1159            return this.modules;
1160        }
1161    
1162        /**
1163         * Sets the modules of the instance.
1164         *
1165         * @param value The new modules of the instance.
1166         *
1167         * @see #getModules()
1168         */
1169        public void setModules( final Modules value )
1170        {
1171            this.modules = value;
1172        }
1173    
1174        /**
1175         * Gets the {@code VelocityEngine} used for generating source code.
1176         *
1177         * @return The {@code VelocityEngine} used for generating source code.
1178         *
1179         * @throws IllegalStateException if initializing a new velocity engine fails.
1180         *
1181         * @see #setVelocityEngine(org.apache.velocity.app.VelocityEngine)
1182         */
1183        public VelocityEngine getVelocityEngine()
1184        {
1185            if ( this.velocityEngine == null )
1186            {
1187                try
1188                {
1189                    final java.util.Properties props = new java.util.Properties();
1190                    props.put( "resource.loader", "class" );
1191                    props.put( "class.resource.loader.class", VELOCITY_RESOURCE_LOADER );
1192                    props.put( "runtime.references.strict", Boolean.TRUE.toString() );
1193    
1194                    final VelocityEngine engine = new VelocityEngine();
1195                    engine.setProperty( RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new LogChute()
1196                    {
1197    
1198                        public void init( final RuntimeServices runtimeServices ) throws Exception
1199                        {
1200                        }
1201    
1202                        public void log( final int level, final String message )
1203                        {
1204                            this.log( level, message, null );
1205                        }
1206    
1207                        public void log( final int level, final String message, final Throwable throwable )
1208                        {
1209                            JomcTool.this.log( this.toToolLevel( level ), message, throwable );
1210                        }
1211    
1212                        public boolean isLevelEnabled( final int level )
1213                        {
1214                            return isLoggable( this.toToolLevel( level ) );
1215                        }
1216    
1217                        private Level toToolLevel( final int logChuteLevel )
1218                        {
1219                            switch ( logChuteLevel )
1220                            {
1221                                case LogChute.DEBUG_ID:
1222                                    return Level.FINE;
1223    
1224                                case LogChute.ERROR_ID:
1225                                    return Level.SEVERE;
1226    
1227                                case LogChute.INFO_ID:
1228                                    return Level.INFO;
1229    
1230                                case LogChute.TRACE_ID:
1231                                    return Level.FINER;
1232    
1233                                case LogChute.WARN_ID:
1234                                    return Level.WARNING;
1235    
1236                                default:
1237                                    return Level.FINEST;
1238    
1239                            }
1240                        }
1241    
1242                    } );
1243    
1244                    engine.init( props );
1245                    this.velocityEngine = engine;
1246                }
1247                catch ( final Exception e )
1248                {
1249                    throw new IllegalStateException( e );
1250                }
1251            }
1252    
1253            return this.velocityEngine;
1254        }
1255    
1256        /**
1257         * Sets the {@code VelocityEngine} of the instance.
1258         *
1259         * @param value The new {@code VelocityEngine} of the instance.
1260         *
1261         * @see #getVelocityEngine()
1262         */
1263        public void setVelocityEngine( final VelocityEngine value )
1264        {
1265            this.velocityEngine = value;
1266        }
1267    
1268        /**
1269         * Gets the velocity context used for merging templates.
1270         *
1271         * @return The velocity context used for merging templates.
1272         */
1273        public VelocityContext getVelocityContext()
1274        {
1275            final Date now = new Date();
1276            final VelocityContext ctx = new VelocityContext();
1277            ctx.put( "modules", this.getModules() );
1278            ctx.put( "tool", this );
1279            ctx.put( "calendar", Calendar.getInstance() );
1280            ctx.put( "now", new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ" ).format( now ) );
1281            ctx.put( "year", new SimpleDateFormat( "yyyy" ).format( now ) );
1282            ctx.put( "month", new SimpleDateFormat( "MM" ).format( now ) );
1283            ctx.put( "day", new SimpleDateFormat( "dd" ).format( now ) );
1284            ctx.put( "hour", new SimpleDateFormat( "HH" ).format( now ) );
1285            ctx.put( "minute", new SimpleDateFormat( "mm" ).format( now ) );
1286            ctx.put( "second", new SimpleDateFormat( "ss" ).format( now ) );
1287            ctx.put( "timezone", new SimpleDateFormat( "Z" ).format( now ) );
1288            return ctx;
1289        }
1290    
1291        /**
1292         * Gets the encoding to use for reading templates.
1293         *
1294         * @return The encoding to use for reading templates.
1295         *
1296         * @see #setTemplateEncoding(java.lang.String)
1297         */
1298        public String getTemplateEncoding()
1299        {
1300            if ( this.templateEncoding == null )
1301            {
1302                this.templateEncoding = this.getMessage( "buildSourceEncoding", null );
1303            }
1304    
1305            return this.templateEncoding;
1306        }
1307    
1308        /**
1309         * Sets the encoding to use for reading templates.
1310         *
1311         * @param value The encoding to use for reading templates.
1312         *
1313         * @see #getTemplateEncoding()
1314         */
1315        public void setTemplateEncoding( final String value )
1316        {
1317            this.templateEncoding = value;
1318            this.templateCache.clear();
1319        }
1320    
1321        /**
1322         * Gets the encoding to use for reading files.
1323         *
1324         * @return The encoding to use for reading files.
1325         *
1326         * @see #setInputEncoding(java.lang.String)
1327         */
1328        public String getInputEncoding()
1329        {
1330            if ( this.inputEncoding == null )
1331            {
1332                this.inputEncoding = new InputStreamReader( new ByteArrayInputStream( NO_BYTES ) ).getEncoding();
1333                if ( this.isLoggable( Level.FINE ) )
1334                {
1335                    this.log( Level.FINE, this.getMessage( "defaultInputEncoding", new Object[]
1336                        {
1337                            this.inputEncoding
1338                        } ), null );
1339    
1340                }
1341            }
1342    
1343            return this.inputEncoding;
1344        }
1345    
1346        /**
1347         * Sets the encoding to use for reading files.
1348         *
1349         * @param value The encoding to use for reading files.
1350         *
1351         * @see #getInputEncoding()
1352         */
1353        public void setInputEncoding( final String value )
1354        {
1355            this.inputEncoding = value;
1356        }
1357    
1358        /**
1359         * Gets the encoding to use for writing files.
1360         *
1361         * @return The encoding to use for writing files.
1362         *
1363         * @see #setOutputEncoding(java.lang.String)
1364         */
1365        public String getOutputEncoding()
1366        {
1367            if ( this.outputEncoding == null )
1368            {
1369                this.outputEncoding = new OutputStreamWriter( new ByteArrayOutputStream() ).getEncoding();
1370                if ( this.isLoggable( Level.FINE ) )
1371                {
1372                    this.log( Level.FINE, this.getMessage( "defaultOutputEncoding", new Object[]
1373                        {
1374                            this.outputEncoding
1375                        } ), null );
1376    
1377                }
1378            }
1379    
1380            return this.outputEncoding;
1381        }
1382    
1383        /**
1384         * Sets the encoding to use for writing files.
1385         *
1386         * @param value The encoding to use for writing files.
1387         *
1388         * @see #getOutputEncoding()
1389         */
1390        public void setOutputEncoding( final String value )
1391        {
1392            this.outputEncoding = value;
1393        }
1394    
1395        /**
1396         * Gets the profile of the instance.
1397         *
1398         * @return The profile of the instance.
1399         *
1400         * @see #setProfile(java.lang.String)
1401         */
1402        public String getProfile()
1403        {
1404            if ( this.profile == null )
1405            {
1406                this.profile = DEFAULT_PROFILE;
1407                if ( this.isLoggable( Level.FINE ) )
1408                {
1409                    this.log( Level.FINE, this.getMessage( "defaultProfile", new Object[]
1410                        {
1411                            this.profile
1412                        } ), null );
1413    
1414                }
1415            }
1416    
1417            return this.profile;
1418        }
1419    
1420        /**
1421         * Sets the profile of the instance.
1422         *
1423         * @param value The profile of the instance.
1424         *
1425         * @see #getProfile()
1426         */
1427        public void setProfile( final String value )
1428        {
1429            this.profile = value;
1430            this.templateCache.clear();
1431        }
1432    
1433        /**
1434         * Gets a velocity template for a given name.
1435         * <p>This method returns the template corresponding to the profile of the instance. If that template is not found,
1436         * the template of the default profile is returned so that only templates differing from the default templates need
1437         * to be provided when exchanging templates.</p>
1438         *
1439         * @param templateName The name of the template to get.
1440         *
1441         * @return The template matching {@code templateName}.
1442         *
1443         * @throws NullPointerException if {@code templateName} is {@code null}.
1444         * @throws IOException if getting the template fails.
1445         *
1446         * @see #getProfile()
1447         * @see #getTemplateEncoding()
1448         */
1449        public Template getVelocityTemplate( final String templateName ) throws IOException
1450        {
1451            if ( templateName == null )
1452            {
1453                throw new NullPointerException( "templateName" );
1454            }
1455    
1456            Template template = this.templateCache.get( templateName );
1457    
1458            if ( template == null )
1459            {
1460                try
1461                {
1462                    template = this.getVelocityEngine().getTemplate(
1463                        TEMPLATE_PREFIX + this.getProfile() + "/" + templateName, this.getTemplateEncoding() );
1464    
1465                    this.templateCache.put( templateName, template );
1466                }
1467                catch ( final ResourceNotFoundException e )
1468                {
1469                    if ( this.isLoggable( Level.CONFIG ) )
1470                    {
1471                        this.log( Level.CONFIG, this.getMessage( "templateNotFound", new Object[]
1472                            {
1473                                templateName, this.getProfile()
1474                            } ), e );
1475    
1476                    }
1477    
1478                    try
1479                    {
1480                        template = this.getVelocityEngine().getTemplate(
1481                            TEMPLATE_PREFIX + DEFAULT_PROFILE + "/" + templateName, this.getTemplateEncoding() );
1482    
1483                        if ( this.isLoggable( Level.CONFIG ) )
1484                        {
1485                            this.log( Level.CONFIG, this.getMessage( "defaultTemplate", new Object[]
1486                                {
1487                                    templateName, DEFAULT_PROFILE
1488                                } ), e );
1489    
1490                        }
1491    
1492                        this.templateCache.put( templateName, template );
1493                    }
1494                    catch ( final ResourceNotFoundException e2 )
1495                    {
1496                        throw (IOException) new IOException( this.getMessage( "templateNotFound", new Object[]
1497                            {
1498                                templateName, DEFAULT_PROFILE
1499                            } ) ).initCause( e2 );
1500    
1501                    }
1502                    catch ( final Exception e2 )
1503                    {
1504                        throw (IOException) new IOException( this.getMessage( "failedGettingTemplate", new Object[]
1505                            {
1506                                templateName
1507                            } ) ).initCause( e2 );
1508    
1509                    }
1510                }
1511                catch ( final Exception e )
1512                {
1513                    throw (IOException) new IOException( this.getMessage( "failedGettingTemplate", new Object[]
1514                        {
1515                            templateName
1516                        } ) ).initCause( e );
1517    
1518                }
1519            }
1520    
1521            return template;
1522        }
1523    
1524        /**
1525         * Notifies registered listeners.
1526         *
1527         * @param level The level of the event.
1528         * @param message The message of the event or {@code null}.
1529         * @param throwable The throwable of the event or {@code null}.
1530         *
1531         * @throws NullPointerException if {@code level} is {@code null}.
1532         *
1533         * @see #getListeners()
1534         */
1535        protected void log( final Level level, final String message, final Throwable throwable )
1536        {
1537            if ( level == null )
1538            {
1539                throw new NullPointerException( "level" );
1540            }
1541    
1542            if ( this.isLoggable( level ) )
1543            {
1544                for ( Listener l : this.getListeners() )
1545                {
1546                    l.onLog( level, message, throwable );
1547                }
1548            }
1549        }
1550    
1551        private String getJavaPackageName( final String identifier )
1552        {
1553            if ( identifier == null )
1554            {
1555                throw new NullPointerException( "identifier" );
1556            }
1557    
1558            final int idx = identifier.lastIndexOf( '.' );
1559            return idx != -1 ? identifier.substring( 0, idx ) : "";
1560        }
1561    
1562        private String getMessage( final String key, final Object args )
1563        {
1564            if ( key == null )
1565            {
1566                throw new NullPointerException( "key" );
1567            }
1568    
1569            final ResourceBundle b = ResourceBundle.getBundle( JomcTool.class.getName().replace( '.', '/' ) );
1570            return args == null ? b.getString( key ) : new MessageFormat( b.getString( key ) ).format( args );
1571        }
1572    
1573    }