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