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