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