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