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