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