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