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