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