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