001 /*
002 * Copyright (C) Christian Schulte, 2005-206
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without
006 * modification, are permitted provided that the following conditions
007 * are met:
008 *
009 * o Redistributions of source code must retain the above copyright
010 * notice, this list of conditions and the following disclaimer.
011 *
012 * o Redistributions in binary form must reproduce the above copyright
013 * notice, this list of conditions and the following disclaimer in
014 * the documentation and/or other materials provided with the
015 * distribution.
016 *
017 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
018 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
019 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
020 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
021 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
022 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
023 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
024 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
026 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027 *
028 * $JOMC: JomcTool.java 4172 2012-01-15 07:49:10Z schulte2005 $
029 *
030 */
031 package org.jomc.tools;
032
033 import java.io.BufferedReader;
034 import java.io.ByteArrayInputStream;
035 import java.io.ByteArrayOutputStream;
036 import java.io.IOException;
037 import java.io.InputStream;
038 import java.io.InputStreamReader;
039 import java.io.OutputStreamWriter;
040 import java.io.StringReader;
041 import java.lang.ref.Reference;
042 import java.lang.ref.SoftReference;
043 import java.lang.reflect.InvocationTargetException;
044 import java.net.URL;
045 import java.text.DateFormat;
046 import java.text.Format;
047 import java.text.MessageFormat;
048 import java.text.SimpleDateFormat;
049 import java.util.ArrayList;
050 import java.util.Calendar;
051 import java.util.Collections;
052 import java.util.Enumeration;
053 import java.util.HashMap;
054 import java.util.LinkedList;
055 import java.util.List;
056 import java.util.Locale;
057 import java.util.Map;
058 import java.util.ResourceBundle;
059 import java.util.logging.Level;
060 import javax.activation.MimeType;
061 import javax.activation.MimeTypeParseException;
062 import org.apache.commons.lang.StringEscapeUtils;
063 import org.apache.commons.lang.StringUtils;
064 import org.apache.velocity.Template;
065 import org.apache.velocity.VelocityContext;
066 import org.apache.velocity.app.VelocityEngine;
067 import org.apache.velocity.exception.ParseErrorException;
068 import org.apache.velocity.exception.ResourceNotFoundException;
069 import org.apache.velocity.exception.VelocityException;
070 import org.apache.velocity.runtime.RuntimeConstants;
071 import org.apache.velocity.runtime.RuntimeServices;
072 import org.apache.velocity.runtime.log.LogChute;
073 import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
074 import org.apache.velocity.runtime.resource.loader.URLResourceLoader;
075 import org.jomc.model.Argument;
076 import org.jomc.model.ArgumentType;
077 import org.jomc.model.Dependency;
078 import org.jomc.model.Implementation;
079 import org.jomc.model.InheritanceModel;
080 import org.jomc.model.Message;
081 import org.jomc.model.ModelObject;
082 import org.jomc.model.Modules;
083 import org.jomc.model.Multiplicity;
084 import org.jomc.model.Properties;
085 import org.jomc.model.Property;
086 import org.jomc.model.Specification;
087 import org.jomc.model.SpecificationReference;
088 import org.jomc.model.Specifications;
089 import org.jomc.model.Text;
090 import org.jomc.model.Texts;
091 import org.jomc.model.modlet.ModelHelper;
092 import org.jomc.modlet.Model;
093
094 /**
095 * Base tool class.
096 *
097 * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a>
098 * @version $JOMC: JomcTool.java 4172 2012-01-15 07:49:10Z schulte2005 $
099 */
100 public class JomcTool
101 {
102
103 /** Listener interface. */
104 public abstract static class Listener
105 {
106
107 /** Creates a new {@code Listener} instance. */
108 public Listener()
109 {
110 super();
111 }
112
113 /**
114 * Gets called on logging.
115 *
116 * @param level The level of the event.
117 * @param message The message of the event or {@code null}.
118 * @param throwable The throwable of the event or {@code null}.
119 *
120 * @throws NullPointerException if {@code level} is {@code null}.
121 */
122 public void onLog( final Level level, final String message, final Throwable throwable )
123 {
124 if ( level == null )
125 {
126 throw new NullPointerException( "level" );
127 }
128 }
129
130 }
131
132 /** Empty byte array. */
133 private static final byte[] NO_BYTES =
134 {
135 };
136
137 /** The prefix of the template location. */
138 private static final String TEMPLATE_PREFIX =
139 JomcTool.class.getPackage().getName().replace( '.', '/' ) + "/templates/";
140
141 /** Constant for the default template profile. */
142 private static final String DEFAULT_TEMPLATE_PROFILE = "jomc-java";
143
144 /** The default template profile. */
145 private static volatile String defaultTemplateProfile;
146
147 /**
148 * The log level events are logged at by default.
149 * @see #getDefaultLogLevel()
150 */
151 private static final Level DEFAULT_LOG_LEVEL = Level.WARNING;
152
153 /** The default log level. */
154 private static volatile Level defaultLogLevel;
155
156 /** The model of the instance. */
157 private Model model;
158
159 /** The {@code VelocityEngine} of the instance. */
160 private VelocityEngine velocityEngine;
161
162 /** The encoding to use for reading templates. */
163 private String templateEncoding;
164
165 /**
166 * The location to search for templates in addition to searching the class path.
167 * @since 1.2
168 */
169 private URL templateLocation;
170
171 /** The encoding to use for reading files. */
172 private String inputEncoding;
173
174 /** The encoding to use for writing files. */
175 private String outputEncoding;
176
177 /**
178 * The template parameters.
179 * @since 1.2
180 */
181 private Map<String, Object> templateParameters;
182
183 /** The template profile of the instance. */
184 private String templateProfile;
185
186 /** The indentation string of the instance. */
187 private String indentation;
188
189 /** The line separator of the instance. */
190 private String lineSeparator;
191
192 /** The listeners of the instance. */
193 private List<Listener> listeners;
194
195 /** The log level of the instance. */
196 private Level logLevel;
197
198 /**
199 * The locale of the instance.
200 * @since 1.2
201 */
202 private Locale locale;
203
204 /** Cached indentation strings. */
205 private volatile Reference<Map<String, String>> indentationCache;
206
207 /** Cached template locations. */
208 private volatile Reference<Map<String, String>> templateLocationsCache;
209
210 /** Cached template profile properties. */
211 private volatile Reference<Map<String, java.util.Properties>> templateProfilePropertiesCache;
212
213 /** Creates a new {@code JomcTool} instance. */
214 public JomcTool()
215 {
216 super();
217 }
218
219 /**
220 * Creates a new {@code JomcTool} instance taking a {@code JomcTool} instance to initialize the new instance with.
221 *
222 * @param tool The instance to initialize the new instance with.
223 *
224 * @throws NullPointerException if {@code tool} is {@code null}.
225 * @throws IOException if copying {@code tool} fails.
226 */
227 public JomcTool( final JomcTool tool ) throws IOException
228 {
229 this();
230
231 if ( tool == null )
232 {
233 throw new NullPointerException( "tool" );
234 }
235
236 this.indentation = tool.indentation;
237 this.inputEncoding = tool.inputEncoding;
238 this.lineSeparator = tool.lineSeparator;
239 this.listeners = tool.listeners != null ? new LinkedList<Listener>( tool.listeners ) : null;
240 this.logLevel = tool.logLevel;
241 this.model = tool.model != null ? tool.model.clone() : null;
242 this.outputEncoding = tool.outputEncoding;
243 this.templateEncoding = tool.templateEncoding;
244 this.templateProfile = tool.templateProfile;
245 this.velocityEngine = tool.velocityEngine;
246 this.locale = tool.locale;
247 this.templateParameters =
248 tool.templateParameters != null ? new HashMap<String, Object>( tool.templateParameters ) : null;
249
250 this.templateLocation =
251 tool.templateLocation != null ? new URL( tool.templateLocation.toExternalForm() ) : null;
252
253 }
254
255 /**
256 * Gets the list of registered listeners.
257 * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
258 * to the returned list will be present inside the object. This is why there is no {@code set} method for the
259 * listeners property.</p>
260 *
261 * @return The list of registered listeners.
262 *
263 * @see #log(java.util.logging.Level, java.lang.String, java.lang.Throwable)
264 */
265 public List<Listener> getListeners()
266 {
267 if ( this.listeners == null )
268 {
269 this.listeners = new LinkedList<Listener>();
270 }
271
272 return this.listeners;
273 }
274
275 /**
276 * Gets the default log level events are logged at.
277 * <p>The default log level is controlled by system property {@code org.jomc.tools.JomcTool.defaultLogLevel} holding
278 * the log level to log events at by default. If that property is not set, the {@code WARNING} default is
279 * returned.</p>
280 *
281 * @return The log level events are logged at by default.
282 *
283 * @see #getLogLevel()
284 * @see Level#parse(java.lang.String)
285 */
286 public static Level getDefaultLogLevel()
287 {
288 if ( defaultLogLevel == null )
289 {
290 defaultLogLevel = Level.parse( System.getProperty( "org.jomc.tools.JomcTool.defaultLogLevel",
291 DEFAULT_LOG_LEVEL.getName() ) );
292
293 }
294
295 return defaultLogLevel;
296 }
297
298 /**
299 * Sets the default log level events are logged at.
300 *
301 * @param value The new default level events are logged at or {@code null}.
302 *
303 * @see #getDefaultLogLevel()
304 */
305 public static void setDefaultLogLevel( final Level value )
306 {
307 defaultLogLevel = value;
308 }
309
310 /**
311 * Gets the log level of the instance.
312 *
313 * @return The log level of the instance.
314 *
315 * @see #getDefaultLogLevel()
316 * @see #setLogLevel(java.util.logging.Level)
317 * @see #isLoggable(java.util.logging.Level)
318 */
319 public final Level getLogLevel()
320 {
321 if ( this.logLevel == null )
322 {
323 this.logLevel = getDefaultLogLevel();
324
325 if ( this.isLoggable( Level.CONFIG ) )
326 {
327 this.log( Level.CONFIG, getMessage( "defaultLogLevelInfo", this.logLevel.getLocalizedName() ), null );
328 }
329 }
330
331 return this.logLevel;
332 }
333
334 /**
335 * Sets the log level of the instance.
336 *
337 * @param value The new log level of the instance or {@code null}.
338 *
339 * @see #getLogLevel()
340 * @see #isLoggable(java.util.logging.Level)
341 */
342 public final void setLogLevel( final Level value )
343 {
344 this.logLevel = value;
345 }
346
347 /**
348 * Checks if a message at a given level is provided to the listeners of the instance.
349 *
350 * @param level The level to test.
351 *
352 * @return {@code true}, if messages at {@code level} are provided to the listeners of the instance;
353 * {@code false}, if messages at {@code level} are not provided to the listeners of the instance.
354 *
355 * @throws NullPointerException if {@code level} is {@code null}.
356 *
357 * @see #getLogLevel()
358 * @see #setLogLevel(java.util.logging.Level)
359 * @see #log(java.util.logging.Level, java.lang.String, java.lang.Throwable)
360 */
361 public boolean isLoggable( final Level level )
362 {
363 if ( level == null )
364 {
365 throw new NullPointerException( "level" );
366 }
367
368 return level.intValue() >= this.getLogLevel().intValue();
369 }
370
371 /**
372 * Gets the Java package name of a specification.
373 *
374 * @param specification The specification to get the Java package name of.
375 *
376 * @return The Java package name of {@code specification} or {@code null}.
377 *
378 * @throws NullPointerException if {@code specification} is {@code null}.
379 */
380 public String getJavaPackageName( final Specification specification )
381 {
382 if ( specification == null )
383 {
384 throw new NullPointerException( "specification" );
385 }
386
387 return specification.getClazz() != null ? this.getJavaPackageName( specification.getClazz() ) : null;
388 }
389
390 /**
391 * Gets the Java type name of a specification.
392 *
393 * @param specification The specification to get the Java type name of.
394 * @param qualified {@code true}, to return the fully qualified type name (with package name prepended);
395 * {@code false}, to return the short type name (without package name prepended).
396 *
397 * @return The Java type name of {@code specification} or {@code null}.
398 *
399 * @throws NullPointerException if {@code specification} is {@code null}.
400 */
401 public String getJavaTypeName( final Specification specification, final boolean qualified )
402 {
403 if ( specification == null )
404 {
405 throw new NullPointerException( "specification" );
406 }
407
408 if ( specification.getClazz() != null )
409 {
410 final StringBuilder typeName = new StringBuilder( specification.getClazz().length() );
411 final String javaPackageName = this.getJavaPackageName( specification );
412
413 if ( qualified && javaPackageName.length() > 0 )
414 {
415 typeName.append( javaPackageName ).append( '.' );
416 }
417
418 typeName.append( javaPackageName.length() > 0
419 ? specification.getClazz().substring( javaPackageName.length() + 1 )
420 : specification.getClazz() );
421
422 return typeName.toString();
423 }
424
425 return null;
426 }
427
428 /**
429 * Gets the Java class path location of a specification.
430 *
431 * @param specification The specification to return the Java class path location of.
432 *
433 * @return The Java class path location of {@code specification} or {@code null}.
434 *
435 * @throws NullPointerException if {@code specification} is {@code null}.
436 */
437 public String getJavaClasspathLocation( final Specification specification )
438 {
439 if ( specification == null )
440 {
441 throw new NullPointerException( "specification" );
442 }
443
444 return specification.getClazz() != null
445 ? ( this.getJavaTypeName( specification, true ) ).replace( '.', '/' )
446 : null;
447
448 }
449
450 /**
451 * Gets the Java package name of a specification reference.
452 *
453 * @param reference The specification reference to get the Java package name of.
454 *
455 * @return The Java package name of {@code reference} or {@code null}.
456 *
457 * @throws NullPointerException if {@code reference} is {@code null}.
458 */
459 public String getJavaPackageName( final SpecificationReference reference )
460 {
461 if ( reference == null )
462 {
463 throw new NullPointerException( "reference" );
464 }
465
466 final Specification s = this.getModules().getSpecification( reference.getIdentifier() );
467 assert s != null : "Specification '" + reference.getIdentifier() + "' not found.";
468 return s.getClazz() != null ? this.getJavaPackageName( s ) : null;
469 }
470
471 /**
472 * Gets the name of a Java type of a given specification reference.
473 *
474 * @param reference The specification reference to get a Java type name of.
475 * @param qualified {@code true}, to return the fully qualified type name (with package name prepended);
476 * {@code false}, to return the short type name (without package name prepended).
477 *
478 * @return The Java type name of {@code reference} or {@code null}.
479 *
480 * @throws NullPointerException if {@code reference} is {@code null}.
481 */
482 public String getJavaTypeName( final SpecificationReference reference, final boolean qualified )
483 {
484 if ( reference == null )
485 {
486 throw new NullPointerException( "reference" );
487 }
488
489 final Specification s = this.getModules().getSpecification( reference.getIdentifier() );
490 assert s != null : "Specification '" + reference.getIdentifier() + "' not found.";
491 return s.getClazz() != null ? this.getJavaTypeName( s, qualified ) : null;
492 }
493
494 /**
495 * Gets the Java package name of an implementation.
496 *
497 * @param implementation The implementation to get the Java package name of.
498 *
499 * @return The Java package name of {@code implementation} or {@code null}.
500 *
501 * @throws NullPointerException if {@code implementation} is {@code null}.
502 */
503 public String getJavaPackageName( final Implementation implementation )
504 {
505 if ( implementation == null )
506 {
507 throw new NullPointerException( "implementation" );
508 }
509
510 return implementation.getClazz() != null ? this.getJavaPackageName( implementation.getClazz() ) : null;
511 }
512
513 /**
514 * Gets the Java type name of an implementation.
515 *
516 * @param implementation The implementation to get the Java type name of.
517 * @param qualified {@code true}, to return the fully qualified type name (with package name prepended);
518 * {@code false}, to return the short type name (without package name prepended).
519 *
520 * @return The Java type name of {@code implementation} or {@code null}.
521 *
522 * @throws NullPointerException if {@code implementation} is {@code null}.
523 */
524 public String getJavaTypeName( final Implementation implementation, final boolean qualified )
525 {
526 if ( implementation == null )
527 {
528 throw new NullPointerException( "implementation" );
529 }
530
531 if ( implementation.getClazz() != null )
532 {
533 final StringBuilder typeName = new StringBuilder( implementation.getClazz().length() );
534 final String javaPackageName = this.getJavaPackageName( implementation );
535
536 if ( qualified && javaPackageName.length() > 0 )
537 {
538 typeName.append( javaPackageName ).append( '.' );
539 }
540
541 typeName.append( javaPackageName.length() > 0
542 ? implementation.getClazz().substring( javaPackageName.length() + 1 )
543 : implementation.getClazz() );
544
545 return typeName.toString();
546 }
547
548 return null;
549 }
550
551 /**
552 * Gets the Java class path location of an implementation.
553 *
554 * @param implementation The implementation to return the Java class path location of.
555 *
556 * @return The Java class path location of {@code implementation} or {@code null}.
557 *
558 * @throws NullPointerException if {@code implementation} is {@code null}.
559 */
560 public String getJavaClasspathLocation( final Implementation implementation )
561 {
562 if ( implementation == null )
563 {
564 throw new NullPointerException( "implementation" );
565 }
566
567 return implementation.getClazz() != null
568 ? ( this.getJavaTypeName( implementation, true ) ).replace( '.', '/' )
569 : null;
570
571 }
572
573 /**
574 * Gets a list of names of all Java types an implementation implements.
575 *
576 * @param implementation The implementation to get names of all implemented Java types of.
577 * @param qualified {@code true}, to return the fully qualified type names (with package name prepended);
578 * {@code false}, to return the short type names (without package name prepended).
579 *
580 * @return An unmodifiable list of names of all Java types implemented by {@code implementation}.
581 *
582 * @throws NullPointerException if {@code implementation} is {@code null}.
583 *
584 * @deprecated As of JOMC 1.2, replaced by method {@link #getImplementedJavaTypeNames(org.jomc.model.Implementation, boolean)}.
585 * This method will be removed in version 2.0.
586 */
587 @Deprecated
588 public List<String> getJavaInterfaceNames( final Implementation implementation, final boolean qualified )
589 {
590 if ( implementation == null )
591 {
592 throw new NullPointerException( "implementation" );
593 }
594
595 return this.getImplementedJavaTypeNames( implementation, qualified );
596 }
597
598 /**
599 * Gets a list of names of all Java types an implementation implements.
600 *
601 * @param implementation The implementation to get names of all implemented Java types of.
602 * @param qualified {@code true}, to return the fully qualified type names (with package name prepended);
603 * {@code false}, to return the short type names (without package name prepended).
604 *
605 * @return An unmodifiable list of names of all Java types implemented by {@code implementation}.
606 *
607 * @throws NullPointerException if {@code implementation} is {@code null}.
608 *
609 * @since 1.2
610 */
611 public List<String> getImplementedJavaTypeNames( final Implementation implementation, final boolean qualified )
612 {
613 if ( implementation == null )
614 {
615 throw new NullPointerException( "implementation" );
616 }
617
618 final Specifications specs = this.getModules().getSpecifications( implementation.getIdentifier() );
619 final List<String> col = new ArrayList<String>( specs == null ? 0 : specs.getSpecification().size() );
620
621 if ( specs != null )
622 {
623 for ( int i = 0, s0 = specs.getSpecification().size(); i < s0; i++ )
624 {
625 final Specification s = specs.getSpecification().get( i );
626
627 if ( s.getClazz() != null )
628 {
629 final String typeName = this.getJavaTypeName( s, qualified );
630 if ( !col.contains( typeName ) )
631 {
632 col.add( typeName );
633 }
634 }
635 }
636 }
637
638 return Collections.unmodifiableList( col );
639 }
640
641 /**
642 * Gets the Java type name of an argument.
643 *
644 * @param argument The argument to get the Java type name of.
645 *
646 * @return The Java type name of {@code argument}.
647 *
648 * @throws NullPointerException if {@code argument} is {@code null}.
649 */
650 public String getJavaTypeName( final Argument argument )
651 {
652 if ( argument == null )
653 {
654 throw new NullPointerException( "argument" );
655 }
656
657 String javaTypeName = "java.lang.String";
658
659 if ( argument.getType() == ArgumentType.DATE || argument.getType() == ArgumentType.TIME )
660 {
661 javaTypeName = "java.util.Date";
662 }
663 else if ( argument.getType() == ArgumentType.NUMBER )
664 {
665 javaTypeName = "java.lang.Number";
666 }
667
668 return javaTypeName;
669 }
670
671 /**
672 * Gets a Java method parameter name of an argument.
673 *
674 * @param argument The argument to get the Java method parameter name of.
675 *
676 * @return The Java method parameter name of {@code argument}.
677 *
678 * @throws NullPointerException if {@code argument} is {@code null}.
679 *
680 * @since 1.2
681 */
682 public String getJavaMethodParameterName( final Argument argument )
683 {
684 if ( argument == null )
685 {
686 throw new NullPointerException( "argument" );
687 }
688
689 return this.getJavaIdentifier( argument.getName(), false );
690 }
691
692 /**
693 * Gets the Java type name of a property.
694 *
695 * @param property The property to get the Java type name of.
696 * @param boxify {@code true}, to return the name of the Java wrapper class when the type is a Java primitive type;
697 * {@code false}, to return the exact binary name (unboxed name) of the Java type.
698 *
699 * @return The Java type name of {@code property}.
700 *
701 * @throws NullPointerException if {@code property} is {@code null}.
702 */
703 public String getJavaTypeName( final Property property, final boolean boxify )
704 {
705 if ( property == null )
706 {
707 throw new NullPointerException( "property" );
708 }
709
710 if ( property.getType() != null )
711 {
712 final String typeName = property.getType();
713
714 if ( boxify )
715 {
716 if ( Boolean.TYPE.getName().equals( typeName ) )
717 {
718 return Boolean.class.getName();
719 }
720 if ( Byte.TYPE.getName().equals( typeName ) )
721 {
722 return Byte.class.getName();
723 }
724 if ( Character.TYPE.getName().equals( typeName ) )
725 {
726 return Character.class.getName();
727 }
728 if ( Double.TYPE.getName().equals( typeName ) )
729 {
730 return Double.class.getName();
731 }
732 if ( Float.TYPE.getName().equals( typeName ) )
733 {
734 return Float.class.getName();
735 }
736 if ( Integer.TYPE.getName().equals( typeName ) )
737 {
738 return Integer.class.getName();
739 }
740 if ( Long.TYPE.getName().equals( typeName ) )
741 {
742 return Long.class.getName();
743 }
744 if ( Short.TYPE.getName().equals( typeName ) )
745 {
746 return Short.class.getName();
747 }
748 }
749
750 return typeName;
751 }
752
753 return property.getAny() != null ? Object.class.getName() : String.class.getName();
754 }
755
756 /**
757 * Gets a flag indicating the type of a given property is a Java primitive.
758 *
759 * @param property The property to query.
760 *
761 * @return {@code true}, if the Java type of {@code property} is primitive; {@code false}, if not.
762 *
763 * @throws NullPointerException if {@code property} is {@code null}.
764 */
765 public boolean isJavaPrimitiveType( final Property property )
766 {
767 if ( property == null )
768 {
769 throw new NullPointerException( "property" );
770 }
771
772 return !this.getJavaTypeName( property, false ).equals( this.getJavaTypeName( property, true ) );
773 }
774
775 /**
776 * Gets the name of a Java getter method of a given property.
777 *
778 * @param property The property to get a Java getter method name of.
779 *
780 * @return The Java getter method name of {@code property}.
781 *
782 * @throws NullPointerException if {@code property} is {@code null}.
783 */
784 public String getJavaGetterMethodName( final Property property )
785 {
786 if ( property == null )
787 {
788 throw new NullPointerException( "property" );
789 }
790
791 String prefix = "get";
792
793 final String javaTypeName = this.getJavaTypeName( property, true );
794 if ( Boolean.class.getName().equals( javaTypeName ) )
795 {
796 prefix = "is";
797 }
798
799 return prefix + this.getJavaIdentifier( property.getName(), true );
800 }
801
802 /**
803 * Gets the name of a Java setter method of a given property.
804 *
805 * @param property The property to get a Java setter method name of.
806 *
807 * @return The Java setter method name of {@code property}.
808 *
809 * @throws NullPointerException if {@code property} is {@code null}.
810 *
811 * @since 1.2
812 */
813 public String getJavaSetterMethodName( final Property property )
814 {
815 if ( property == null )
816 {
817 throw new NullPointerException( "property" );
818 }
819
820 return "set" + this.getJavaIdentifier( property.getName(), true );
821 }
822
823 /**
824 * Gets a Java method parameter name of a property.
825 *
826 * @param property The property to get the Java method parameter name of.
827 *
828 * @return The Java method parameter name of {@code property}.
829 *
830 * @throws NullPointerException if {@code property} is {@code null}.
831 *
832 * @since 1.2
833 */
834 public String getJavaMethodParameterName( final Property property )
835 {
836 if ( property == null )
837 {
838 throw new NullPointerException( "property" );
839 }
840
841 return this.getJavaIdentifier( property.getName(), false );
842 }
843
844 /**
845 * Gets the name of a Java type of a given dependency.
846 *
847 * @param dependency The dependency to get a dependency Java type name of.
848 *
849 * @return The Java type name of {@code dependency} or {@code null}.
850 *
851 * @throws NullPointerException if {@code dependency} is {@code null}.
852 */
853 public String getJavaTypeName( final Dependency dependency )
854 {
855 if ( dependency == null )
856 {
857 throw new NullPointerException( "dependency" );
858 }
859
860 final Specification s = this.getModules().getSpecification( dependency.getIdentifier() );
861
862 if ( s != null && s.getClazz() != null )
863 {
864 final StringBuilder typeName = new StringBuilder( s.getClazz().length() );
865 typeName.append( this.getJavaTypeName( s, true ) );
866 if ( s.getMultiplicity() == Multiplicity.MANY && dependency.getImplementationName() == null )
867 {
868 typeName.append( "[]" );
869 }
870
871 return typeName.toString();
872 }
873
874 return null;
875 }
876
877 /**
878 * Gets the name of a Java getter method of a given dependency.
879 *
880 * @param dependency The dependency to get a Java getter method name of.
881 *
882 * @return The Java getter method name of {@code dependency}.
883 *
884 * @throws NullPointerException if {@code dependency} is {@code null}.
885 */
886 public String getJavaGetterMethodName( final Dependency dependency )
887 {
888 if ( dependency == null )
889 {
890 throw new NullPointerException( "dependency" );
891 }
892
893 return "get" + this.getJavaIdentifier( dependency.getName(), true );
894 }
895
896 /**
897 * Gets the name of a Java setter method of a given dependency.
898 *
899 * @param dependency The dependency to get a Java setter method name of.
900 *
901 * @return The Java setter method name of {@code dependency}.
902 *
903 * @throws NullPointerException if {@code dependency} is {@code null}.
904 *
905 * @since 1.2
906 */
907 public String getJavaSetterMethodName( final Dependency dependency )
908 {
909 if ( dependency == null )
910 {
911 throw new NullPointerException( "dependency" );
912 }
913
914 return "set" + this.getJavaIdentifier( dependency.getName(), true );
915 }
916
917 /**
918 * Gets a Java method parameter name of a dependency.
919 *
920 * @param dependency The dependency to get the Java method parameter name of.
921 *
922 * @return The Java method parameter name of {@code dependency}.
923 *
924 * @throws NullPointerException if {@code dependency} is {@code null}.
925 *
926 * @since 1.2
927 */
928 public String getJavaMethodParameterName( final Dependency dependency )
929 {
930 if ( dependency == null )
931 {
932 throw new NullPointerException( "dependency" );
933 }
934
935 return this.getJavaIdentifier( dependency.getName(), false );
936 }
937
938 /**
939 * Gets the name of a Java getter method of a given message.
940 *
941 * @param message The message to get a Java getter method name of.
942 *
943 * @return The Java getter method name of {@code message}.
944 *
945 * @throws NullPointerException if {@code message} is {@code null}.
946 */
947 public String getJavaGetterMethodName( final Message message )
948 {
949 if ( message == null )
950 {
951 throw new NullPointerException( "message" );
952 }
953
954 return "get" + this.getJavaIdentifier( message.getName(), true );
955 }
956
957 /**
958 * Gets the name of a Java setter method of a given message.
959 *
960 * @param message The message to get a Java setter method name of.
961 *
962 * @return The Java setter method name of {@code message}.
963 *
964 * @throws NullPointerException if {@code message} is {@code null}.
965 *
966 * @since 1.2
967 */
968 public String getJavaSetterMethodName( final Message message )
969 {
970 if ( message == null )
971 {
972 throw new NullPointerException( "message" );
973 }
974
975 return "set" + this.getJavaIdentifier( message.getName(), true );
976 }
977
978 /**
979 * Gets a Java method parameter name of a message.
980 *
981 * @param message The message to get the Java method parameter name of.
982 *
983 * @return The Java method parameter name of {@code message}.
984 *
985 * @throws NullPointerException if {@code message} is {@code null}.
986 *
987 * @since 1.2
988 */
989 public String getJavaMethodParameterName( final Message message )
990 {
991 if ( message == null )
992 {
993 throw new NullPointerException( "message" );
994 }
995
996 return this.getJavaIdentifier( message.getName(), false );
997 }
998
999 /**
1000 * Gets the Java modifier name of a dependency of a given implementation.
1001 *
1002 * @param implementation The implementation declaring the dependency to get a Java modifier name of.
1003 * @param dependency The dependency to get a Java modifier name of.
1004 *
1005 * @return The Java modifier name of {@code dependency} of {@code implementation}.
1006 *
1007 * @throws NullPointerException if {@code implementation} or {@code dependency} is {@code null}.
1008 */
1009 public String getJavaModifierName( final Implementation implementation, final Dependency dependency )
1010 {
1011 if ( implementation == null )
1012 {
1013 throw new NullPointerException( "implementation" );
1014 }
1015 if ( dependency == null )
1016 {
1017 throw new NullPointerException( "dependency" );
1018 }
1019
1020 return "private";
1021 }
1022
1023 /**
1024 * Gets the Java modifier name of a message of a given implementation.
1025 *
1026 * @param implementation The implementation declaring the message to get a Java modifier name of.
1027 * @param message The message to get a Java modifier name of.
1028 *
1029 * @return The Java modifier name of {@code message} of {@code implementation}.
1030 *
1031 * @throws NullPointerException if {@code implementation} or {@code message} is {@code null}.
1032 */
1033 public String getJavaModifierName( final Implementation implementation, final Message message )
1034 {
1035 if ( implementation == null )
1036 {
1037 throw new NullPointerException( "implementation" );
1038 }
1039 if ( message == null )
1040 {
1041 throw new NullPointerException( "message" );
1042 }
1043
1044 return "private";
1045 }
1046
1047 /**
1048 * Gets the Java modifier name of a property of a given implementation.
1049 *
1050 * @param implementation The implementation declaring the property to get a Java modifier name of.
1051 * @param property The property to get a Java modifier name of.
1052 *
1053 * @return The Java modifier name of {@code property} of {@code implementation}.
1054 *
1055 * @throws NullPointerException if {@code implementation} or {@code property} is {@code null}.
1056 */
1057 public String getJavaModifierName( final Implementation implementation, final Property property )
1058 {
1059 if ( implementation == null )
1060 {
1061 throw new NullPointerException( "implementation" );
1062 }
1063 if ( property == null )
1064 {
1065 throw new NullPointerException( "property" );
1066 }
1067
1068 String modifier = "private";
1069 final Properties specified = this.getModules().getSpecifiedProperties( implementation.getIdentifier() );
1070
1071 if ( specified != null && specified.getProperty( property.getName() ) != null )
1072 {
1073 modifier = "public";
1074 }
1075
1076 return modifier;
1077 }
1078
1079 /**
1080 * Formats a text to a Javadoc comment.
1081 *
1082 * @param text The text to format to a Javadoc comment.
1083 * @param indentationLevel The indentation level of the comment.
1084 * @param linePrefix The text to prepend lines with.
1085 *
1086 * @return {@code text} formatted to a Javadoc comment.
1087 *
1088 * @throws NullPointerException if {@code text} or {@code linePrefix} is {@code null}.
1089 * @throws IllegalArgumentException if {@code indentationLevel} is negative.
1090 */
1091 public String getJavadocComment( final Text text, final int indentationLevel, final String linePrefix )
1092 {
1093 if ( text == null )
1094 {
1095 throw new NullPointerException( "text" );
1096 }
1097 if ( linePrefix == null )
1098 {
1099 throw new NullPointerException( "linePrefix" );
1100 }
1101 if ( indentationLevel < 0 )
1102 {
1103 throw new IllegalArgumentException( Integer.toString( indentationLevel ) );
1104 }
1105
1106 BufferedReader reader = null;
1107 boolean suppressExceptionOnClose = true;
1108
1109 try
1110 {
1111 String javadoc = "";
1112
1113 if ( text.getValue() != null )
1114 {
1115 final String indent = this.getIndentation( indentationLevel );
1116 reader = new BufferedReader( new StringReader( text.getValue() ) );
1117 final StringBuilder builder = new StringBuilder( text.getValue().length() );
1118
1119 String line;
1120 while ( ( line = reader.readLine() ) != null )
1121 {
1122 builder.append( this.getLineSeparator() ).append( indent ).append( linePrefix ).
1123 append( line.replaceAll( "\\/\\*\\*", "/*" ).replaceAll( "\\*/", "/" ) );
1124
1125 }
1126
1127 if ( builder.length() > 0 )
1128 {
1129 javadoc =
1130 builder.substring( this.getLineSeparator().length() + indent.length() + linePrefix.length() );
1131
1132 if ( !new MimeType( text.getType() ).match( "text/html" ) )
1133 {
1134 javadoc = StringEscapeUtils.escapeHtml( javadoc );
1135 }
1136 }
1137 }
1138
1139 suppressExceptionOnClose = false;
1140 return javadoc;
1141 }
1142 catch ( final MimeTypeParseException e )
1143 {
1144 throw new AssertionError( e );
1145 }
1146 catch ( final IOException e )
1147 {
1148 throw new AssertionError( e );
1149 }
1150 finally
1151 {
1152 try
1153 {
1154 if ( reader != null )
1155 {
1156 reader.close();
1157 }
1158 }
1159 catch ( final IOException e )
1160 {
1161 if ( suppressExceptionOnClose )
1162 {
1163 this.log( Level.SEVERE, getMessage( e ), e );
1164 }
1165 else
1166 {
1167 throw new AssertionError( e );
1168 }
1169 }
1170 }
1171 }
1172
1173 /**
1174 * Formats a text from a list of texts to a Javadoc comment.
1175 *
1176 * @param texts The list of texts to format to a Javadoc comment.
1177 * @param indentationLevel The indentation level of the comment.
1178 * @param linePrefix The text to prepend lines with.
1179 *
1180 * @return The text corresponding to the locale of the instance from the list of texts formatted to a Javadoc
1181 * comment.
1182 *
1183 * @throws NullPointerException if {@code texts} or {@code linePrefix} is {@code null}.
1184 * @throws IllegalArgumentException if {@code indentationLevel} is negative.
1185 *
1186 * @see #getLocale()
1187 *
1188 * @since 1.2
1189 */
1190 public String getJavadocComment( final Texts texts, final int indentationLevel, final String linePrefix )
1191 {
1192 if ( texts == null )
1193 {
1194 throw new NullPointerException( "texts" );
1195 }
1196 if ( linePrefix == null )
1197 {
1198 throw new NullPointerException( "linePrefix" );
1199 }
1200 if ( indentationLevel < 0 )
1201 {
1202 throw new IllegalArgumentException( Integer.toString( indentationLevel ) );
1203 }
1204
1205 return this.getJavadocComment( texts.getText( this.getLocale().getLanguage() ), indentationLevel, linePrefix );
1206 }
1207
1208 /**
1209 * Formats a string to a Java string with unicode escapes.
1210 *
1211 * @param str The string to format to a Java string or {@code null}.
1212 *
1213 * @return {@code str} formatted to a Java string or {@code null}.
1214 *
1215 * @see StringEscapeUtils#escapeJava(java.lang.String)
1216 */
1217 public String getJavaString( final String str )
1218 {
1219 return StringEscapeUtils.escapeJava( str );
1220 }
1221
1222 /**
1223 * Formats a string to a Java identifier.
1224 *
1225 * @param str The string to format or {@code null}.
1226 * @param capitalize {@code true}, to return an identifier with the first character upper cased; {@code false}, to
1227 * return an identifier with the first character lower cased.
1228 *
1229 * @return {@code str} formatted to a Java identifier or {@code null}.
1230 *
1231 * @since 1.2
1232 */
1233 public String getJavaIdentifier( final String str, final boolean capitalize )
1234 {
1235 String identifier = null;
1236
1237 if ( str != null )
1238 {
1239 final int len = str.length();
1240 final StringBuilder builder = new StringBuilder( len );
1241 boolean uc = capitalize;
1242
1243 for ( int i = 0; i < len; i++ )
1244 {
1245 final char c = str.charAt( i );
1246
1247 if ( !( Character.isJavaIdentifierStart( c ) || Character.isJavaIdentifierPart( c ) ) )
1248 {
1249 uc = true;
1250 }
1251 else if ( builder.length() == 0
1252 ? Character.isJavaIdentifierStart( c )
1253 : Character.isJavaIdentifierPart( c ) )
1254 {
1255 builder.append( uc ? Character.toUpperCase( c ) : c );
1256 uc = false;
1257 }
1258 }
1259
1260 identifier = builder.toString();
1261 }
1262
1263 return identifier;
1264 }
1265
1266 /**
1267 * Gets a flag indicating the class of a given specification is located in the Java default package.
1268 *
1269 * @param specification The specification to query.
1270 *
1271 * @return {@code true}, if the class of {@code specification} is located in the Java default package;
1272 * {@code false}, else.
1273 *
1274 * @throws NullPointerException if {@code specification} is {@code null}.
1275 */
1276 public boolean isJavaDefaultPackage( final Specification specification )
1277 {
1278 if ( specification == null )
1279 {
1280 throw new NullPointerException( "specification" );
1281 }
1282
1283 return specification.getClazz() != null && this.getJavaPackageName( specification ).length() == 0;
1284 }
1285
1286 /**
1287 * Gets a flag indicating the class of a given implementation is located in the Java default package.
1288 *
1289 * @param implementation The implementation to query.
1290 *
1291 * @return {@code true}, if the class of {@code implementation} is located in the Java default package;
1292 * {@code false}, else.
1293 *
1294 * @throws NullPointerException if {@code implementation} is {@code null}.
1295 */
1296 public boolean isJavaDefaultPackage( final Implementation implementation )
1297 {
1298 if ( implementation == null )
1299 {
1300 throw new NullPointerException( "implementation" );
1301 }
1302
1303 return implementation.getClazz() != null && this.getJavaPackageName( implementation ).length() == 0;
1304 }
1305
1306 /**
1307 * Formats a string to a HTML string with HTML entities.
1308 *
1309 * @param str The string to format to a HTML string with HTML entities or {@code null}.
1310 *
1311 * @return {@code str} formatted to a HTML string with HTML entities or {@code null}.
1312 *
1313 * @see StringEscapeUtils#escapeHtml(java.lang.String)
1314 *
1315 * @since 1.2
1316 */
1317 public String getHtmlString( final String str )
1318 {
1319 return StringEscapeUtils.escapeHtml( str );
1320 }
1321
1322 /**
1323 * Formats a string to a XML string with XML entities.
1324 *
1325 * @param str The string to format to a XML string with XML entities or {@code null}.
1326 *
1327 * @return {@code str} formatted to a XML string with XML entities or {@code null}.
1328 *
1329 * @see StringEscapeUtils#escapeXml(java.lang.String)
1330 *
1331 * @since 1.2
1332 */
1333 public String getXmlString( final String str )
1334 {
1335 return StringEscapeUtils.escapeXml( str );
1336 }
1337
1338 /**
1339 * Formats a string to a JavaScript string applying JavaScript string rules.
1340 *
1341 * @param str The string to format to a JavaScript string by applying JavaScript string rules or {@code null}.
1342 *
1343 * @return {@code str} formatted to a JavaScript string with JavaScript string rules applied or {@code null}.
1344 *
1345 * @see StringEscapeUtils#escapeJavaScript(java.lang.String)
1346 *
1347 * @since 1.2
1348 */
1349 public String getJavaScriptString( final String str )
1350 {
1351 return StringEscapeUtils.escapeJavaScript( str );
1352 }
1353
1354 /**
1355 * Formats a string to a SQL string.
1356 *
1357 * @param str The string to format to a SQL string or {@code null}.
1358 *
1359 * @return {@code str} formatted to a SQL string or {@code null}.
1360 *
1361 * @see StringEscapeUtils#escapeSql(java.lang.String)
1362 *
1363 * @since 1.2
1364 */
1365 public String getSqlString( final String str )
1366 {
1367 return StringEscapeUtils.escapeSql( str );
1368 }
1369
1370 /**
1371 * Formats a string to a CSV string.
1372 *
1373 * @param str The string to format to a CSV string or {@code null}.
1374 *
1375 * @return {@code str} formatted to a CSV string or {@code null}.
1376 *
1377 * @see StringEscapeUtils#escapeCsv(java.lang.String)
1378 *
1379 * @since 1.2
1380 */
1381 public String getCsvString( final String str )
1382 {
1383 return StringEscapeUtils.escapeCsv( str );
1384 }
1385
1386 /**
1387 * Formats a {@code Boolean} to a string.
1388 *
1389 * @param b The {@code Boolean} to format to a string or {@code null}.
1390 *
1391 * @return {@code b} formatted to a string.
1392 *
1393 * @see #getLocale()
1394 *
1395 * @since 1.2
1396 */
1397 public String getBooleanString( final Boolean b )
1398 {
1399 final MessageFormat messageFormat = new MessageFormat( ResourceBundle.getBundle(
1400 JomcTool.class.getName().replace( '.', '/' ), this.getLocale() ).
1401 getString( b ? "booleanStringTrue" : "booleanStringFalse" ), this.getLocale() );
1402
1403 return messageFormat.format( null );
1404 }
1405
1406 /**
1407 * Gets the display language of a given language code.
1408 *
1409 * @param language The language code to get the display language of.
1410 *
1411 * @return The display language of {@code language}.
1412 *
1413 * @throws NullPointerException if {@code language} is {@code null}.
1414 */
1415 public String getDisplayLanguage( final String language )
1416 {
1417 if ( language == null )
1418 {
1419 throw new NullPointerException( "language" );
1420 }
1421
1422 final Locale l = new Locale( language );
1423 return l.getDisplayLanguage( l );
1424 }
1425
1426 /**
1427 * Formats a calendar instance to a string.
1428 *
1429 * @param calendar The calendar to format to a string.
1430 *
1431 * @return The date of {@code calendar} formatted using a short format style pattern.
1432 *
1433 * @throws NullPointerException if {@code calendar} is {@code null}.
1434 *
1435 * @see DateFormat#SHORT
1436 */
1437 public String getShortDate( final Calendar calendar )
1438 {
1439 if ( calendar == null )
1440 {
1441 throw new NullPointerException( "calendar" );
1442 }
1443
1444 return DateFormat.getDateInstance( DateFormat.SHORT, this.getLocale() ).format( calendar.getTime() );
1445 }
1446
1447 /**
1448 * Formats a calendar instance to a string.
1449 *
1450 * @param calendar The calendar to format to a string.
1451 *
1452 * @return The date of {@code calendar} formatted using a medium format style pattern.
1453 *
1454 * @throws NullPointerException if {@code calendar} is {@code null}.
1455 *
1456 * @see DateFormat#MEDIUM
1457 *
1458 * @since 1.2
1459 */
1460 public String getMediumDate( final Calendar calendar )
1461 {
1462 if ( calendar == null )
1463 {
1464 throw new NullPointerException( "calendar" );
1465 }
1466
1467 return DateFormat.getDateInstance( DateFormat.MEDIUM, this.getLocale() ).format( calendar.getTime() );
1468 }
1469
1470 /**
1471 * Formats a calendar instance to a string.
1472 *
1473 * @param calendar The calendar to format to a string.
1474 *
1475 * @return The date of {@code calendar} formatted using a long format style pattern.
1476 *
1477 * @throws NullPointerException if {@code calendar} is {@code null}.
1478 *
1479 * @see DateFormat#LONG
1480 */
1481 public String getLongDate( final Calendar calendar )
1482 {
1483 if ( calendar == null )
1484 {
1485 throw new NullPointerException( "calendar" );
1486 }
1487
1488 return DateFormat.getDateInstance( DateFormat.LONG, this.getLocale() ).format( calendar.getTime() );
1489 }
1490
1491 /**
1492 * Formats a calendar instance to a string.
1493 *
1494 * @param calendar The calendar to format to a string.
1495 *
1496 * @return The date of {@code calendar} formatted using an ISO-8601 format style.
1497 *
1498 * @throws NullPointerException if {@code calendar} is {@code null}.
1499 *
1500 * @see SimpleDateFormat yyyy-DDD
1501 *
1502 * @since 1.2
1503 */
1504 public String getIsoDate( final Calendar calendar )
1505 {
1506 if ( calendar == null )
1507 {
1508 throw new NullPointerException( "calendar" );
1509 }
1510
1511 return new SimpleDateFormat( "yyyy-DDD", this.getLocale() ).format( calendar.getTime() );
1512 }
1513
1514 /**
1515 * Formats a calendar instance to a string.
1516 *
1517 * @param calendar The calendar to format to a string.
1518 *
1519 * @return The time of {@code calendar} formatted using a short format style pattern.
1520 *
1521 * @throws NullPointerException if {@code calendar} is {@code null}.
1522 *
1523 * @see DateFormat#SHORT
1524 */
1525 public String getShortTime( final Calendar calendar )
1526 {
1527 if ( calendar == null )
1528 {
1529 throw new NullPointerException( "calendar" );
1530 }
1531
1532 return DateFormat.getTimeInstance( DateFormat.SHORT, this.getLocale() ).format( calendar.getTime() );
1533 }
1534
1535 /**
1536 * Formats a calendar instance to a string.
1537 *
1538 * @param calendar The calendar to format to a string.
1539 *
1540 * @return The time of {@code calendar} formatted using a medium format style pattern.
1541 *
1542 * @throws NullPointerException if {@code calendar} is {@code null}.
1543 *
1544 * @see DateFormat#MEDIUM
1545 *
1546 * @since 1.2
1547 */
1548 public String getMediumTime( final Calendar calendar )
1549 {
1550 if ( calendar == null )
1551 {
1552 throw new NullPointerException( "calendar" );
1553 }
1554
1555 return DateFormat.getTimeInstance( DateFormat.MEDIUM, this.getLocale() ).format( calendar.getTime() );
1556 }
1557
1558 /**
1559 * Formats a calendar instance to a string.
1560 *
1561 * @param calendar The calendar to format to a string.
1562 *
1563 * @return The time of {@code calendar} formatted using a long format style pattern.
1564 *
1565 * @throws NullPointerException if {@code calendar} is {@code null}.
1566 *
1567 * @see DateFormat#LONG
1568 */
1569 public String getLongTime( final Calendar calendar )
1570 {
1571 if ( calendar == null )
1572 {
1573 throw new NullPointerException( "calendar" );
1574 }
1575
1576 return DateFormat.getTimeInstance( DateFormat.LONG, this.getLocale() ).format( calendar.getTime() );
1577 }
1578
1579 /**
1580 * Formats a calendar instance to a string.
1581 *
1582 * @param calendar The calendar to format to a string.
1583 *
1584 * @return The time of {@code calendar} formatted using an ISO-8601 format style.
1585 *
1586 * @throws NullPointerException if {@code calendar} is {@code null}.
1587 *
1588 * @see SimpleDateFormat HH:mm
1589 *
1590 * @since 1.2
1591 */
1592 public String getIsoTime( final Calendar calendar )
1593 {
1594 if ( calendar == null )
1595 {
1596 throw new NullPointerException( "calendar" );
1597 }
1598
1599 return new SimpleDateFormat( "HH:mm", this.getLocale() ).format( calendar.getTime() );
1600 }
1601
1602 /**
1603 * Formats a calendar instance to a string.
1604 *
1605 * @param calendar The calendar to format to a string.
1606 *
1607 * @return The date and time of {@code calendar} formatted using a short format style pattern.
1608 *
1609 * @throws NullPointerException if {@code calendar} is {@code null}.
1610 *
1611 * @see DateFormat#SHORT
1612 */
1613 public String getShortDateTime( final Calendar calendar )
1614 {
1615 if ( calendar == null )
1616 {
1617 throw new NullPointerException( "calendar" );
1618 }
1619
1620 return DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT, this.getLocale() ).
1621 format( calendar.getTime() );
1622
1623 }
1624
1625 /**
1626 * Formats a calendar instance to a string.
1627 *
1628 * @param calendar The calendar to format to a string.
1629 *
1630 * @return The date and time of {@code calendar} formatted using a medium format style pattern.
1631 *
1632 * @throws NullPointerException if {@code calendar} is {@code null}.
1633 *
1634 * @see DateFormat#MEDIUM
1635 *
1636 * @since 1.2
1637 */
1638 public String getMediumDateTime( final Calendar calendar )
1639 {
1640 if ( calendar == null )
1641 {
1642 throw new NullPointerException( "calendar" );
1643 }
1644
1645 return DateFormat.getDateTimeInstance( DateFormat.MEDIUM, DateFormat.MEDIUM, this.getLocale() ).
1646 format( calendar.getTime() );
1647
1648 }
1649
1650 /**
1651 * Formats a calendar instance to a string.
1652 *
1653 * @param calendar The calendar to format to a string.
1654 *
1655 * @return The date and time of {@code calendar} formatted using a long format style pattern.
1656 *
1657 * @throws NullPointerException if {@code calendar} is {@code null}.
1658 *
1659 * @see DateFormat#LONG
1660 */
1661 public String getLongDateTime( final Calendar calendar )
1662 {
1663 if ( calendar == null )
1664 {
1665 throw new NullPointerException( "calendar" );
1666 }
1667
1668 return DateFormat.getDateTimeInstance( DateFormat.LONG, DateFormat.LONG, this.getLocale() ).
1669 format( calendar.getTime() );
1670
1671 }
1672
1673 /**
1674 * Formats a calendar instance to a string.
1675 *
1676 * @param calendar The calendar to format to a string.
1677 *
1678 * @return The date and time of {@code calendar} formatted using a ISO-8601 format style.
1679 *
1680 * @throws NullPointerException if {@code calendar} is {@code null}.
1681 *
1682 * @see SimpleDateFormat yyyy-MM-dd'T'HH:mm:ssZ
1683 *
1684 * @since 1.2
1685 */
1686 public String getIsoDateTime( final Calendar calendar )
1687 {
1688 if ( calendar == null )
1689 {
1690 throw new NullPointerException( "calendar" );
1691 }
1692
1693 // JDK: As of JDK 7, "yyyy-MM-dd'T'HH:mm:ssXXX".
1694 return new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ssZ", this.getLocale() ).format( calendar.getTime() );
1695 }
1696
1697 /**
1698 * Gets a string describing the range of years for given calendars.
1699 *
1700 * @param start The start of the range.
1701 * @param end The end of the range.
1702 *
1703 * @return Formatted range of the years of {@code start} and {@code end} (e.g. {@code "start - end"}).
1704 *
1705 * @throws NullPointerException if {@code start} or {@code end} is {@code null}.
1706 */
1707 public String getYears( final Calendar start, final Calendar end )
1708 {
1709 if ( start == null )
1710 {
1711 throw new NullPointerException( "start" );
1712 }
1713 if ( end == null )
1714 {
1715 throw new NullPointerException( "end" );
1716 }
1717
1718 final Format yearFormat = new SimpleDateFormat( "yyyy", this.getLocale() );
1719 final int s = start.get( Calendar.YEAR );
1720 final int e = end.get( Calendar.YEAR );
1721 final StringBuilder years = new StringBuilder();
1722
1723 if ( s != e )
1724 {
1725 if ( s < e )
1726 {
1727 years.append( yearFormat.format( start.getTime() ) ).append( " - " ).
1728 append( yearFormat.format( end.getTime() ) );
1729
1730 }
1731 else
1732 {
1733 years.append( yearFormat.format( end.getTime() ) ).append( " - " ).
1734 append( yearFormat.format( start.getTime() ) );
1735
1736 }
1737 }
1738 else
1739 {
1740 years.append( yearFormat.format( start.getTime() ) );
1741 }
1742
1743 return years.toString();
1744 }
1745
1746 /**
1747 * Gets the model of the instance.
1748 *
1749 * @return The model of the instance.
1750 *
1751 * @see #getModules()
1752 * @see #setModel(org.jomc.modlet.Model)
1753 */
1754 public final Model getModel()
1755 {
1756 if ( this.model == null )
1757 {
1758 this.model = new Model();
1759 this.model.setIdentifier( ModelObject.MODEL_PUBLIC_ID );
1760 }
1761
1762 return this.model;
1763 }
1764
1765 /**
1766 * Sets the model of the instance.
1767 *
1768 * @param value The new model of the instance or {@code null}.
1769 *
1770 * @see #getModel()
1771 */
1772 public final void setModel( final Model value )
1773 {
1774 this.model = value;
1775 }
1776
1777 /**
1778 * Gets the modules of the instance.
1779 *
1780 * @return The modules of the instance.
1781 *
1782 * @see #getModel()
1783 * @see #setModel(org.jomc.modlet.Model)
1784 *
1785 * @deprecated As of JOMC 1.2, please use method {@link #getModel()} and {@link ModelHelper#getModules(org.jomc.modlet.Model)}.
1786 * This method will be removed in version 2.0.
1787 */
1788 @Deprecated
1789 public Modules getModules()
1790 {
1791 Modules modules = ModelHelper.getModules( this.getModel() );
1792
1793 if ( modules == null )
1794 {
1795 modules = new Modules();
1796 ModelHelper.setModules( this.getModel(), modules );
1797 }
1798
1799 return modules;
1800 }
1801
1802 /**
1803 * Gets the {@code VelocityEngine} of the instance.
1804 *
1805 * @return The {@code VelocityEngine} of the instance.
1806 *
1807 * @throws IOException if initializing a new velocity engine fails.
1808 *
1809 * @see #setVelocityEngine(org.apache.velocity.app.VelocityEngine)
1810 */
1811 public final VelocityEngine getVelocityEngine() throws IOException
1812 {
1813 if ( this.velocityEngine == null )
1814 {
1815 /** {@code LogChute} logging to the listeners of the tool. */
1816 class JomcLogChute implements LogChute
1817 {
1818
1819 JomcLogChute()
1820 {
1821 super();
1822 }
1823
1824 public void init( final RuntimeServices runtimeServices ) throws Exception
1825 {
1826 }
1827
1828 public void log( final int level, final String message )
1829 {
1830 this.log( level, message, null );
1831 }
1832
1833 public void log( final int level, final String message, final Throwable throwable )
1834 {
1835 JomcTool.this.log( Level.FINEST, message, throwable );
1836 }
1837
1838 public boolean isLevelEnabled( final int level )
1839 {
1840 return isLoggable( Level.FINEST );
1841 }
1842
1843 }
1844
1845 final VelocityEngine engine = new VelocityEngine();
1846 engine.setProperty( RuntimeConstants.RUNTIME_REFERENCES_STRICT, Boolean.TRUE.toString() );
1847 engine.setProperty( RuntimeConstants.VM_ARGUMENTS_STRICT, Boolean.TRUE.toString() );
1848 engine.setProperty( RuntimeConstants.STRICT_MATH, Boolean.TRUE.toString() );
1849 engine.setProperty( RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new JomcLogChute() );
1850
1851 engine.setProperty( RuntimeConstants.RESOURCE_LOADER, "class" );
1852 engine.setProperty( "class.resource.loader.class", ClasspathResourceLoader.class.getName() );
1853 engine.setProperty( "class.resource.loader.cache", Boolean.TRUE.toString() );
1854
1855 if ( this.getTemplateLocation() != null )
1856 {
1857 engine.setProperty( RuntimeConstants.RESOURCE_LOADER, "class,url" );
1858 engine.setProperty( "url.resource.loader.class", URLResourceLoader.class.getName() );
1859 engine.setProperty( "url.resource.loader.cache", Boolean.TRUE.toString() );
1860 engine.setProperty( "url.resource.loader.root", this.getTemplateLocation().toExternalForm() );
1861 engine.setProperty( "url.resource.loader.timeout", Integer.toString( 60000 ) );
1862 }
1863
1864 this.velocityEngine = engine;
1865 }
1866
1867 return this.velocityEngine;
1868 }
1869
1870 /**
1871 * Sets the {@code VelocityEngine} of the instance.
1872 *
1873 * @param value The new {@code VelocityEngine} of the instance or {@code null}.
1874 *
1875 * @see #getVelocityEngine()
1876 */
1877 public final void setVelocityEngine( final VelocityEngine value )
1878 {
1879 this.velocityEngine = value;
1880 }
1881
1882 /**
1883 * Gets a new velocity context used for merging templates.
1884 *
1885 * @return A new velocity context used for merging templates.
1886 *
1887 * @see #getTemplateParameters()
1888 */
1889 public VelocityContext getVelocityContext()
1890 {
1891 final Calendar now = Calendar.getInstance();
1892 final VelocityContext ctx = new VelocityContext( Collections.synchronizedMap(
1893 new HashMap<String, Object>( this.getTemplateParameters() ) ) );
1894
1895 this.mergeTemplateProfileProperties( this.getTemplateProfile(), this.getLocale().getLanguage(), ctx );
1896 this.mergeTemplateProfileProperties( this.getTemplateProfile(), null, ctx );
1897 this.mergeTemplateProfileProperties( getDefaultTemplateProfile(), this.getLocale().getLanguage(), ctx );
1898 this.mergeTemplateProfileProperties( getDefaultTemplateProfile(), null, ctx );
1899
1900 this.getModules(); // Initialization prior to cloning.
1901 final Model clonedModel = this.getModel().clone();
1902 final Modules clonedModules = ModelHelper.getModules( clonedModel );
1903 assert clonedModules != null : "Unexpected missing modules for model '" + clonedModel.getIdentifier() + "'.";
1904
1905 ctx.put( "model", clonedModel );
1906 ctx.put( "modules", clonedModules );
1907 ctx.put( "imodel", new InheritanceModel( this.getModules() ) );
1908 ctx.put( "tool", this );
1909 ctx.put( "toolName", this.getClass().getName() );
1910 ctx.put( "toolVersion", getMessage( "projectVersion" ) );
1911 ctx.put( "toolUrl", getMessage( "projectUrl" ) );
1912 ctx.put( "calendar", now.getTime() );
1913
1914 // JDK: As of JDK 7, "yyyy-MM-dd'T'HH:mm:ss.SSSXXX".
1915 ctx.put( "now",
1916 new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ", this.getLocale() ).format( now.getTime() ) );
1917
1918 ctx.put( "year", new SimpleDateFormat( "yyyy", this.getLocale() ).format( now.getTime() ) );
1919 ctx.put( "month", new SimpleDateFormat( "MM", this.getLocale() ).format( now.getTime() ) );
1920 ctx.put( "day", new SimpleDateFormat( "dd", this.getLocale() ).format( now.getTime() ) );
1921 ctx.put( "hour", new SimpleDateFormat( "HH", this.getLocale() ).format( now.getTime() ) );
1922 ctx.put( "minute", new SimpleDateFormat( "mm", this.getLocale() ).format( now.getTime() ) );
1923 ctx.put( "second", new SimpleDateFormat( "ss", this.getLocale() ).format( now.getTime() ) );
1924 ctx.put( "timezone", new SimpleDateFormat( "Z", this.getLocale() ).format( now.getTime() ) );
1925 ctx.put( "shortDate", this.getShortDate( now ) );
1926 ctx.put( "mediumDate", this.getMediumDate( now ) );
1927 ctx.put( "longDate", this.getLongDate( now ) );
1928 ctx.put( "isoDate", this.getIsoDate( now ) );
1929 ctx.put( "shortTime", this.getShortTime( now ) );
1930 ctx.put( "mediumTime", this.getMediumTime( now ) );
1931 ctx.put( "longTime", this.getLongTime( now ) );
1932 ctx.put( "isoTime", this.getIsoTime( now ) );
1933 ctx.put( "shortDateTime", this.getShortDateTime( now ) );
1934 ctx.put( "mediumDateTime", this.getMediumDateTime( now ) );
1935 ctx.put( "longDateTime", this.getLongDateTime( now ) );
1936 ctx.put( "isoDateTime", this.getIsoDateTime( now ) );
1937
1938 return ctx;
1939 }
1940
1941 /**
1942 * Gets the template parameters of the instance.
1943 * <p>This accessor method returns a reference to the live map, not a snapshot. Therefore any modification you make
1944 * to the returned map will be present inside the object. This is why there is no {@code set} method for the
1945 * template parameters property.</p>
1946 *
1947 * @return The template parameters of the instance.
1948 *
1949 * @see #getVelocityContext()
1950 *
1951 * @since 1.2
1952 */
1953 public final Map<String, Object> getTemplateParameters()
1954 {
1955 if ( this.templateParameters == null )
1956 {
1957 this.templateParameters = new HashMap<String, Object>();
1958 }
1959
1960 return this.templateParameters;
1961 }
1962
1963 /**
1964 * Gets the location to search for templates in addition to searching the class path.
1965 *
1966 * @return The location to search for templates in addition to searching the class path or {@code null}.
1967 *
1968 * @see #setTemplateLocation(java.net.URL)
1969 *
1970 * @since 1.2
1971 */
1972 public final URL getTemplateLocation()
1973 {
1974 return this.templateLocation;
1975 }
1976
1977 /**
1978 * Sets the location to search for templates in addition to searching the class path.
1979 *
1980 * @param value The new location to search for templates in addition to searching the class path or {@code null}.
1981 *
1982 * @see #getTemplateLocation()
1983 *
1984 * @since 1.2
1985 */
1986 public final void setTemplateLocation( final URL value )
1987 {
1988 this.templateLocation = value;
1989 }
1990
1991 /**
1992 * Gets the encoding to use for reading templates.
1993 *
1994 * @return The encoding to use for reading templates.
1995 *
1996 * @see #setTemplateEncoding(java.lang.String)
1997 */
1998 public final String getTemplateEncoding()
1999 {
2000 if ( this.templateEncoding == null )
2001 {
2002 this.templateEncoding = getMessage( "buildSourceEncoding" );
2003
2004 if ( this.isLoggable( Level.CONFIG ) )
2005 {
2006 this.log( Level.CONFIG, getMessage( "defaultTemplateEncoding", this.templateEncoding ), null );
2007 }
2008 }
2009
2010 return this.templateEncoding;
2011 }
2012
2013 /**
2014 * Sets the encoding to use for reading templates.
2015 *
2016 * @param value The new encoding to use for reading templates or {@code null}.
2017 *
2018 * @see #getTemplateEncoding()
2019 */
2020 public final void setTemplateEncoding( final String value )
2021 {
2022 this.templateEncoding = value;
2023 this.velocityEngine = null;
2024 }
2025
2026 /**
2027 * Gets the encoding to use for reading files.
2028 *
2029 * @return The encoding to use for reading files.
2030 *
2031 * @see #setInputEncoding(java.lang.String)
2032 */
2033 public final String getInputEncoding()
2034 {
2035 if ( this.inputEncoding == null )
2036 {
2037 this.inputEncoding = new InputStreamReader( new ByteArrayInputStream( NO_BYTES ) ).getEncoding();
2038
2039 if ( this.isLoggable( Level.CONFIG ) )
2040 {
2041 this.log( Level.CONFIG, getMessage( "defaultInputEncoding", this.inputEncoding ), null );
2042 }
2043 }
2044
2045 return this.inputEncoding;
2046 }
2047
2048 /**
2049 * Sets the encoding to use for reading files.
2050 *
2051 * @param value The new encoding to use for reading files or {@code null}.
2052 *
2053 * @see #getInputEncoding()
2054 */
2055 public final void setInputEncoding( final String value )
2056 {
2057 this.inputEncoding = value;
2058 }
2059
2060 /**
2061 * Gets the encoding to use for writing files.
2062 *
2063 * @return The encoding to use for writing files.
2064 *
2065 * @see #setOutputEncoding(java.lang.String)
2066 */
2067 public final String getOutputEncoding()
2068 {
2069 if ( this.outputEncoding == null )
2070 {
2071 this.outputEncoding = new OutputStreamWriter( new ByteArrayOutputStream() ).getEncoding();
2072
2073 if ( this.isLoggable( Level.CONFIG ) )
2074 {
2075 this.log( Level.CONFIG, getMessage( "defaultOutputEncoding", this.outputEncoding ), null );
2076 }
2077 }
2078
2079 return this.outputEncoding;
2080 }
2081
2082 /**
2083 * Sets the encoding to use for writing files.
2084 *
2085 * @param value The encoding to use for writing files or {@code null}.
2086 *
2087 * @see #getOutputEncoding()
2088 */
2089 public final void setOutputEncoding( final String value )
2090 {
2091 this.outputEncoding = value;
2092 }
2093
2094 /**
2095 * Gets the default template profile.
2096 * <p>The default template profile is controlled by system property
2097 * {@code org.jomc.tools.JomcTool.defaultTemplateProfile} holding the name of the template profile to use by
2098 * default. If that property is not set, the {@code jomc-java} default is returned.</p>
2099 *
2100 * @return The default template profile.
2101 *
2102 * @see #setDefaultTemplateProfile(java.lang.String)
2103 *
2104 * @deprecated The {@code static} modifier of this method and support to setup the default template profile using
2105 * a system property will be removed in version 2.0.
2106 */
2107 @Deprecated
2108 public static String getDefaultTemplateProfile()
2109 {
2110 if ( defaultTemplateProfile == null )
2111 {
2112 defaultTemplateProfile = System.getProperty( "org.jomc.tools.JomcTool.defaultTemplateProfile",
2113 DEFAULT_TEMPLATE_PROFILE );
2114
2115 }
2116
2117 return defaultTemplateProfile;
2118 }
2119
2120 /**
2121 * Sets the default template profile.
2122 *
2123 * @param value The new default template profile or {@code null}.
2124 *
2125 * @see #getDefaultTemplateProfile()
2126 *
2127 * @deprecated The {@code static} modifier of this method will be removed in version 2.0.
2128 */
2129 @Deprecated
2130 public static void setDefaultTemplateProfile( final String value )
2131 {
2132 defaultTemplateProfile = value;
2133 }
2134
2135 /**
2136 * Gets the template profile of the instance.
2137 *
2138 * @return The template profile of the instance.
2139 *
2140 * @see #getDefaultTemplateProfile()
2141 * @see #setTemplateProfile(java.lang.String)
2142 */
2143 public final String getTemplateProfile()
2144 {
2145 if ( this.templateProfile == null )
2146 {
2147 this.templateProfile = getDefaultTemplateProfile();
2148
2149 if ( this.isLoggable( Level.CONFIG ) )
2150 {
2151 this.log( Level.CONFIG, getMessage( "defaultTemplateProfile", this.templateProfile ), null );
2152 }
2153 }
2154
2155 return this.templateProfile;
2156 }
2157
2158 /**
2159 * Sets the template profile of the instance.
2160 *
2161 * @param value The new template profile of the instance or {@code null}.
2162 *
2163 * @see #getTemplateProfile()
2164 */
2165 public final void setTemplateProfile( final String value )
2166 {
2167 this.templateProfile = value;
2168 }
2169
2170 /**
2171 * Gets the indentation string of the instance.
2172 *
2173 * @return The indentation string of the instance.
2174 *
2175 * @see #setIndentation(java.lang.String)
2176 */
2177 public final String getIndentation()
2178 {
2179 if ( this.indentation == null )
2180 {
2181 this.indentation = " ";
2182
2183 if ( this.isLoggable( Level.CONFIG ) )
2184 {
2185 this.log( Level.CONFIG, getMessage( "defaultIndentation",
2186 StringEscapeUtils.escapeJava( this.indentation ) ), null );
2187
2188 }
2189 }
2190
2191 return this.indentation;
2192 }
2193
2194 /**
2195 * Gets an indentation string for a given indentation level.
2196 *
2197 * @param level The indentation level to get an indentation string for.
2198 *
2199 * @return The indentation string for {@code level}.
2200 *
2201 * @throws IllegalArgumentException if {@code level} is negative.
2202 *
2203 * @see #getIndentation()
2204 */
2205 public final String getIndentation( final int level )
2206 {
2207 if ( level < 0 )
2208 {
2209 throw new IllegalArgumentException( Integer.toString( level ) );
2210 }
2211
2212 Map<String, String> map = this.indentationCache == null ? null : this.indentationCache.get();
2213
2214 if ( map == null )
2215 {
2216 map = new HashMap<String, String>();
2217 this.indentationCache = new SoftReference<Map<String, String>>( map );
2218 }
2219
2220 final String key = this.getIndentation() + "|" + level;
2221 String idt = map.get( key );
2222
2223 if ( idt == null )
2224 {
2225 final StringBuilder b = new StringBuilder( this.getIndentation().length() * level );
2226
2227 for ( int i = level; i > 0; i-- )
2228 {
2229 b.append( this.getIndentation() );
2230 }
2231
2232 idt = b.toString();
2233 map.put( key, idt );
2234 }
2235
2236 return idt;
2237 }
2238
2239 /**
2240 * Sets the indentation string of the instance.
2241 *
2242 * @param value The new indentation string of the instance or {@code null}.
2243 *
2244 * @see #getIndentation()
2245 */
2246 public final void setIndentation( final String value )
2247 {
2248 this.indentation = value;
2249 }
2250
2251 /**
2252 * Gets the line separator of the instance.
2253 *
2254 * @return The line separator of the instance.
2255 *
2256 * @see #setLineSeparator(java.lang.String)
2257 */
2258 public final String getLineSeparator()
2259 {
2260 if ( this.lineSeparator == null )
2261 {
2262 this.lineSeparator = System.getProperty( "line.separator", "\n" );
2263
2264 if ( this.isLoggable( Level.CONFIG ) )
2265 {
2266 this.log( Level.CONFIG, getMessage( "defaultLineSeparator",
2267 StringEscapeUtils.escapeJava( this.lineSeparator ) ), null );
2268
2269 }
2270 }
2271
2272 return this.lineSeparator;
2273 }
2274
2275 /**
2276 * Sets the line separator of the instance.
2277 *
2278 * @param value The new line separator of the instance or {@code null}.
2279 *
2280 * @see #getLineSeparator()
2281 */
2282 public final void setLineSeparator( final String value )
2283 {
2284 this.lineSeparator = value;
2285 }
2286
2287 /**
2288 * Gets the locale of the instance.
2289 *
2290 * @return The locale of the instance.
2291 *
2292 * @see #setLocale(java.util.Locale)
2293 *
2294 * @since 1.2
2295 */
2296 public final Locale getLocale()
2297 {
2298 if ( this.locale == null )
2299 {
2300 this.locale = Locale.ENGLISH;
2301
2302 if ( this.isLoggable( Level.CONFIG ) )
2303 {
2304 this.log( Level.CONFIG, getMessage( "defaultLocale", this.locale ), null );
2305 }
2306 }
2307
2308 return this.locale;
2309 }
2310
2311 /**
2312 * Sets the locale of the instance.
2313 *
2314 * @param value The new locale of the instance or {@code null}.
2315 *
2316 * @see #getLocale()
2317 *
2318 * @since 1.2
2319 */
2320 public final void setLocale( final Locale value )
2321 {
2322 this.locale = value;
2323 }
2324
2325 /**
2326 * Gets a velocity template for a given name.
2327 * <p>This method searches templates at the following locations in the shown order.
2328 * <ol>
2329 * <li><code>org/jomc/tools/templates/{@link #getTemplateProfile() profile}/{@link #getLocale() language}/<i>templateName</i></code></li>
2330 * <li><code>org/jomc/tools/templates/{@link #getTemplateProfile() profile}/<i>templateName</i></code></li>
2331 * <li><code>org/jomc/tools/templates/{@link #getDefaultTemplateProfile() default profile}/{@link #getLocale() language}/<i>templateName</i></code></li>
2332 * <li><code>org/jomc/tools/templates/{@link #getDefaultTemplateProfile() default profile}/<i>templateName</i></code></li>
2333 * </ol></p>
2334 *
2335 * @param templateName The name of the template to get.
2336 *
2337 * @return The template matching {@code templateName}.
2338 *
2339 * @throws NullPointerException if {@code templateName} is {@code null}.
2340 * @throws IOException if getting the template fails.
2341 *
2342 * @see #getLocale()
2343 * @see #getTemplateProfile()
2344 * @see #getTemplateEncoding()
2345 * @see #getVelocityEngine()
2346 */
2347 public Template getVelocityTemplate( final String templateName ) throws IOException
2348 {
2349 if ( templateName == null )
2350 {
2351 throw new NullPointerException( "templateName" );
2352 }
2353
2354 String location = null;
2355 Template template = null;
2356 final String key = this.getLocale() + "|" + this.getTemplateProfile() + "|" + getDefaultTemplateProfile()
2357 + "|" + templateName;
2358
2359 Map<String, String> map = this.templateLocationsCache == null ? null : this.templateLocationsCache.get();
2360
2361 if ( map == null )
2362 {
2363 map = new HashMap<String, String>( 32 );
2364 this.templateLocationsCache = new SoftReference<Map<String, String>>( map );
2365 }
2366
2367 location = map.get( key );
2368
2369 if ( location == null && !map.containsKey( key ) )
2370 {
2371 if ( !StringUtils.EMPTY.equals( this.getLocale().getLanguage() ) )
2372 {
2373 location = TEMPLATE_PREFIX + this.getTemplateProfile() + "/" + this.getLocale().getLanguage() + "/"
2374 + templateName;
2375
2376 template = this.findVelocityTemplate( location );
2377 }
2378
2379 if ( template == null )
2380 {
2381 location = TEMPLATE_PREFIX + this.getTemplateProfile() + "/" + templateName;
2382 template = this.findVelocityTemplate( location );
2383 }
2384
2385 if ( template == null && !StringUtils.EMPTY.equals( this.getLocale().getLanguage() ) )
2386 {
2387 location = TEMPLATE_PREFIX + getDefaultTemplateProfile() + "/" + this.getLocale().getLanguage() + "/"
2388 + templateName;
2389
2390 template = this.findVelocityTemplate( location );
2391 }
2392
2393 if ( template == null )
2394 {
2395 location = TEMPLATE_PREFIX + getDefaultTemplateProfile() + "/" + templateName;
2396 template = this.findVelocityTemplate( location );
2397 }
2398
2399 map.put( key, location );
2400 }
2401 else if ( location != null )
2402 {
2403 template = this.findVelocityTemplate( location );
2404 }
2405
2406 if ( template == null )
2407 {
2408 throw new IOException( getMessage( "noSuchTemplate", templateName ) );
2409 }
2410
2411 if ( this.isLoggable( Level.FINER ) )
2412 {
2413 this.log( Level.FINER, getMessage( "templateInfo", templateName, location ), null );
2414 }
2415
2416 return template;
2417 }
2418
2419 /**
2420 * Notifies registered listeners.
2421 *
2422 * @param level The level of the event.
2423 * @param message The message of the event or {@code null}.
2424 * @param throwable The throwable of the event or {@code null}.
2425 *
2426 * @throws NullPointerException if {@code level} is {@code null}.
2427 *
2428 * @see #getListeners()
2429 * @see #isLoggable(java.util.logging.Level)
2430 */
2431 public void log( final Level level, final String message, final Throwable throwable )
2432 {
2433 if ( level == null )
2434 {
2435 throw new NullPointerException( "level" );
2436 }
2437
2438 if ( this.isLoggable( level ) )
2439 {
2440 for ( int i = this.getListeners().size() - 1; i >= 0; i-- )
2441 {
2442 this.getListeners().get( i ).onLog( level, message, throwable );
2443 }
2444 }
2445 }
2446
2447 private String getJavaPackageName( final String identifier )
2448 {
2449 if ( identifier == null )
2450 {
2451 throw new NullPointerException( "identifier" );
2452 }
2453
2454 final int idx = identifier.lastIndexOf( '.' );
2455 return idx != -1 ? identifier.substring( 0, idx ) : "";
2456 }
2457
2458 private Template findVelocityTemplate( final String location ) throws IOException
2459 {
2460 try
2461 {
2462 return this.getVelocityEngine().getTemplate( location, this.getTemplateEncoding() );
2463 }
2464 catch ( final ResourceNotFoundException e )
2465 {
2466 if ( this.isLoggable( Level.FINER ) )
2467 {
2468 this.log( Level.FINER, getMessage( "templateNotFound", location ), null );
2469 }
2470
2471 return null;
2472 }
2473 catch ( final ParseErrorException e )
2474 {
2475 String m = getMessage( e );
2476 m = m == null ? "" : " " + m;
2477
2478 // JDK: As of JDK 6, "new IOException( message, cause )".
2479 throw (IOException) new IOException( getMessage( "invalidTemplate", location, m ) ).initCause( e );
2480 }
2481 catch ( final VelocityException e )
2482 {
2483 String m = getMessage( e );
2484 m = m == null ? "" : " " + m;
2485
2486 // JDK: As of JDK 6, "new IOException( message, cause )".
2487 throw (IOException) new IOException( getMessage( "velocityException", location, m ) ).initCause( e );
2488 }
2489 }
2490
2491 private java.util.Properties getTemplateProfileProperties( final String profileName, final String language )
2492 {
2493 Map<String, java.util.Properties> map =
2494 this.templateProfilePropertiesCache == null ? null : this.templateProfilePropertiesCache.get();
2495
2496 if ( map == null )
2497 {
2498 map = new HashMap<String, java.util.Properties>();
2499 this.templateProfilePropertiesCache = new SoftReference<Map<String, java.util.Properties>>( map );
2500 }
2501
2502 final String key = profileName + "|" + language;
2503 java.util.Properties profileProperties = map.get( key );
2504
2505 if ( profileProperties == null )
2506 {
2507 InputStream in = null;
2508 profileProperties = new java.util.Properties();
2509 final String resourceName = "/" + TEMPLATE_PREFIX + profileName + ( language == null ? "" : "/" + language )
2510 + "/context.properties";
2511
2512 try
2513 {
2514 in = this.getClass().getResourceAsStream( resourceName );
2515
2516 if ( in != null )
2517 {
2518 if ( this.isLoggable( Level.CONFIG ) )
2519 {
2520 this.log( Level.CONFIG, getMessage( "contextPropertiesFound", resourceName ), null );
2521 }
2522
2523 profileProperties.load( in );
2524 }
2525 else if ( this.isLoggable( Level.CONFIG ) )
2526 {
2527 this.log( Level.CONFIG, getMessage( "contextPropertiesNotFound", resourceName ), null );
2528 }
2529
2530 map.put( key, profileProperties );
2531 }
2532 catch ( final IOException e )
2533 {
2534 this.log( Level.SEVERE, getMessage( e ), e );
2535 }
2536 finally
2537 {
2538 try
2539 {
2540 if ( in != null )
2541 {
2542 in.close();
2543 }
2544 }
2545 catch ( final IOException e )
2546 {
2547 this.log( Level.SEVERE, getMessage( e ), e );
2548 }
2549 }
2550 }
2551
2552 return profileProperties;
2553 }
2554
2555 private void mergeTemplateProfileProperties( final String profileName, final String language,
2556 final VelocityContext velocityContext )
2557 {
2558 final java.util.Properties templateProfileProperties =
2559 this.getTemplateProfileProperties( profileName, language );
2560
2561 for ( final Enumeration<?> e = templateProfileProperties.propertyNames(); e.hasMoreElements(); )
2562 {
2563 final String name = e.nextElement().toString();
2564 final String value = templateProfileProperties.getProperty( name );
2565 final String[] values = value.split( "\\|" );
2566
2567 if ( !velocityContext.containsKey( name ) )
2568 {
2569 if ( values.length > 1 )
2570 {
2571 try
2572 {
2573 final Class<?> valueClass = Class.forName( values[0] );
2574
2575 if ( values[1].length() > 0 )
2576 {
2577 velocityContext.put(
2578 name, valueClass.getConstructor( String.class ).newInstance( values[1] ) );
2579
2580 }
2581 else
2582 {
2583 velocityContext.put( name, valueClass.newInstance() );
2584 }
2585 }
2586 catch ( final InstantiationException ex )
2587 {
2588 this.log( Level.SEVERE, getMessage( ex ), ex );
2589 }
2590 catch ( final IllegalAccessException ex )
2591 {
2592 this.log( Level.SEVERE, getMessage( ex ), ex );
2593 }
2594 catch ( final InvocationTargetException ex )
2595 {
2596 this.log( Level.SEVERE, getMessage( ex ), ex );
2597 }
2598 catch ( final NoSuchMethodException ex )
2599 {
2600 this.log( Level.SEVERE, getMessage( ex ), ex );
2601 }
2602 catch ( final ClassNotFoundException ex )
2603 {
2604 this.log( Level.SEVERE, getMessage( ex ), ex );
2605 }
2606 }
2607 else
2608 {
2609 velocityContext.put( name, value );
2610 }
2611 }
2612 }
2613 }
2614
2615 private static String getMessage( final String key, final Object... arguments )
2616 {
2617 return MessageFormat.format( ResourceBundle.getBundle(
2618 JomcTool.class.getName().replace( '.', '/' ) ).getString( key ), arguments );
2619
2620 }
2621
2622 private static String getMessage( final Throwable t )
2623 {
2624 return t != null ? t.getMessage() != null ? t.getMessage() : getMessage( t.getCause() ) : null;
2625 }
2626
2627 }