001 /* 002 * Copyright (C) Christian Schulte, 2005-206 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without 006 * modification, are permitted provided that the following conditions 007 * are met: 008 * 009 * o Redistributions of source code must retain the above copyright 010 * notice, this list of conditions and the following disclaimer. 011 * 012 * o Redistributions in binary form must reproduce the above copyright 013 * notice, this list of conditions and the following disclaimer in 014 * the documentation and/or other materials provided with the 015 * distribution. 016 * 017 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 018 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 019 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 020 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, 021 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 022 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 023 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 024 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 025 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 026 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 027 * 028 * $JOMC: JomcTool.java 4365 2012-03-03 21:35:34Z schulte2005 $ 029 * 030 */ 031 package org.jomc.tools; 032 033 import java.io.BufferedReader; 034 import java.io.ByteArrayInputStream; 035 import java.io.ByteArrayOutputStream; 036 import java.io.IOException; 037 import java.io.InputStream; 038 import java.io.InputStreamReader; 039 import java.io.OutputStreamWriter; 040 import java.io.Reader; 041 import java.io.StringReader; 042 import java.lang.ref.Reference; 043 import java.lang.ref.SoftReference; 044 import java.lang.reflect.InvocationTargetException; 045 import java.net.URL; 046 import java.text.DateFormat; 047 import java.text.Format; 048 import java.text.MessageFormat; 049 import java.text.SimpleDateFormat; 050 import java.util.ArrayList; 051 import java.util.Calendar; 052 import java.util.Collections; 053 import java.util.Enumeration; 054 import java.util.HashMap; 055 import java.util.HashSet; 056 import java.util.LinkedList; 057 import java.util.List; 058 import java.util.Locale; 059 import java.util.Map; 060 import java.util.ResourceBundle; 061 import java.util.Set; 062 import java.util.logging.Level; 063 import javax.activation.MimeType; 064 import javax.activation.MimeTypeParseException; 065 import org.apache.commons.io.IOUtils; 066 import org.apache.commons.lang.StringEscapeUtils; 067 import org.apache.commons.lang.StringUtils; 068 import org.apache.velocity.Template; 069 import org.apache.velocity.VelocityContext; 070 import org.apache.velocity.app.VelocityEngine; 071 import org.apache.velocity.exception.ParseErrorException; 072 import org.apache.velocity.exception.ResourceNotFoundException; 073 import org.apache.velocity.exception.VelocityException; 074 import org.apache.velocity.runtime.RuntimeConstants; 075 import org.apache.velocity.runtime.RuntimeServices; 076 import org.apache.velocity.runtime.log.LogChute; 077 import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; 078 import org.apache.velocity.runtime.resource.loader.URLResourceLoader; 079 import org.jomc.model.Argument; 080 import org.jomc.model.ArgumentType; 081 import org.jomc.model.Dependency; 082 import org.jomc.model.Implementation; 083 import org.jomc.model.InheritanceModel; 084 import org.jomc.model.Message; 085 import org.jomc.model.ModelObject; 086 import org.jomc.model.Modules; 087 import org.jomc.model.Multiplicity; 088 import org.jomc.model.Properties; 089 import org.jomc.model.Property; 090 import org.jomc.model.Specification; 091 import org.jomc.model.SpecificationReference; 092 import org.jomc.model.Specifications; 093 import org.jomc.model.Text; 094 import org.jomc.model.Texts; 095 import org.jomc.model.modlet.ModelHelper; 096 import org.jomc.modlet.Model; 097 098 /** 099 * Base tool class. 100 * 101 * @author <a href="mailto:schulte2005@users.sourceforge.net">Christian Schulte</a> 102 * @version $JOMC: JomcTool.java 4365 2012-03-03 21:35:34Z schulte2005 $ 103 */ 104 public class JomcTool 105 { 106 107 /** Listener interface. */ 108 public abstract static class Listener 109 { 110 111 /** Creates a new {@code Listener} instance. */ 112 public Listener() 113 { 114 super(); 115 } 116 117 /** 118 * Gets called on logging. 119 * 120 * @param level The level of the event. 121 * @param message The message of the event or {@code null}. 122 * @param throwable The throwable of the event or {@code null}. 123 * 124 * @throws NullPointerException if {@code level} is {@code null}. 125 */ 126 public void onLog( final Level level, final String message, final Throwable throwable ) 127 { 128 if ( level == null ) 129 { 130 throw new NullPointerException( "level" ); 131 } 132 } 133 134 } 135 136 /** Empty byte array. */ 137 private static final byte[] NO_BYTES = 138 { 139 }; 140 141 /** The prefix of the template location. */ 142 private static final String TEMPLATE_PREFIX = 143 JomcTool.class.getPackage().getName().replace( '.', '/' ) + "/templates/"; 144 145 /** Constant for the default template profile. */ 146 private static final String DEFAULT_TEMPLATE_PROFILE = "jomc-java"; 147 148 /** The default template profile. */ 149 private static volatile String defaultTemplateProfile; 150 151 /** 152 * The log level events are logged at by default. 153 * @see #getDefaultLogLevel() 154 */ 155 private static final Level DEFAULT_LOG_LEVEL = Level.WARNING; 156 157 /** The default log level. */ 158 private static volatile Level defaultLogLevel; 159 160 /** The model of the instance. */ 161 private Model model; 162 163 /** The {@code VelocityEngine} of the instance. */ 164 private VelocityEngine velocityEngine; 165 166 /** The encoding to use for reading templates. */ 167 private String templateEncoding; 168 169 /** 170 * The location to search for templates in addition to searching the class path. 171 * @since 1.2 172 */ 173 private URL templateLocation; 174 175 /** The encoding to use for reading files. */ 176 private String inputEncoding; 177 178 /** The encoding to use for writing files. */ 179 private String outputEncoding; 180 181 /** 182 * The template parameters. 183 * @since 1.2 184 */ 185 private Map<String, Object> templateParameters; 186 187 /** The template profile of the instance. */ 188 private String templateProfile; 189 190 /** The indentation string of the instance. */ 191 private String indentation; 192 193 /** The line separator of the instance. */ 194 private String lineSeparator; 195 196 /** The listeners of the instance. */ 197 private List<Listener> listeners; 198 199 /** The log level of the instance. */ 200 private Level logLevel; 201 202 /** 203 * The locale of the instance. 204 * @since 1.2 205 */ 206 private Locale locale; 207 208 /** Cached indentation strings. */ 209 private volatile Reference<Map<String, String>> indentationCache; 210 211 /** Cached template locations. */ 212 private volatile Reference<Map<String, String>> templateLocationsCache; 213 214 /** Cached template profile properties. */ 215 private volatile Reference<Map<String, java.util.Properties>> templateProfilePropertiesCache; 216 217 /** Cached Java keywords. */ 218 private volatile Reference<Set<String>> javaKeywordsCache; 219 220 /** Creates a new {@code JomcTool} instance. */ 221 public JomcTool() 222 { 223 super(); 224 } 225 226 /** 227 * Creates a new {@code JomcTool} instance taking a {@code JomcTool} instance to initialize the new instance with. 228 * 229 * @param tool The instance to initialize the new instance with. 230 * 231 * @throws NullPointerException if {@code tool} is {@code null}. 232 * @throws IOException if copying {@code tool} fails. 233 */ 234 public JomcTool( final JomcTool tool ) throws IOException 235 { 236 this(); 237 238 if ( tool == null ) 239 { 240 throw new NullPointerException( "tool" ); 241 } 242 243 this.indentation = tool.indentation; 244 this.inputEncoding = tool.inputEncoding; 245 this.lineSeparator = tool.lineSeparator; 246 this.listeners = tool.listeners != null ? new LinkedList<Listener>( tool.listeners ) : null; 247 this.logLevel = tool.logLevel; 248 this.model = tool.model != null ? tool.model.clone() : null; 249 this.outputEncoding = tool.outputEncoding; 250 this.templateEncoding = tool.templateEncoding; 251 this.templateProfile = tool.templateProfile; 252 this.velocityEngine = tool.velocityEngine; 253 this.locale = tool.locale; 254 this.templateParameters = 255 tool.templateParameters != null ? new HashMap<String, Object>( tool.templateParameters ) : null; 256 257 this.templateLocation = 258 tool.templateLocation != null ? new URL( tool.templateLocation.toExternalForm() ) : null; 259 260 } 261 262 /** 263 * Gets the list of registered listeners. 264 * <p>This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make 265 * to the returned list will be present inside the object. This is why there is no {@code set} method for the 266 * listeners property.</p> 267 * 268 * @return The list of registered listeners. 269 * 270 * @see #log(java.util.logging.Level, java.lang.String, java.lang.Throwable) 271 */ 272 public List<Listener> getListeners() 273 { 274 if ( this.listeners == null ) 275 { 276 this.listeners = new LinkedList<Listener>(); 277 } 278 279 return this.listeners; 280 } 281 282 /** 283 * Gets the default log level events are logged at. 284 * <p>The default log level is controlled by system property {@code org.jomc.tools.JomcTool.defaultLogLevel} holding 285 * the log level to log events at by default. If that property is not set, the {@code WARNING} default is 286 * returned.</p> 287 * 288 * @return The log level events are logged at by default. 289 * 290 * @see #getLogLevel() 291 * @see Level#parse(java.lang.String) 292 */ 293 public static Level getDefaultLogLevel() 294 { 295 if ( defaultLogLevel == null ) 296 { 297 defaultLogLevel = Level.parse( System.getProperty( "org.jomc.tools.JomcTool.defaultLogLevel", 298 DEFAULT_LOG_LEVEL.getName() ) ); 299 300 } 301 302 return defaultLogLevel; 303 } 304 305 /** 306 * Sets the default log level events are logged at. 307 * 308 * @param value The new default level events are logged at or {@code null}. 309 * 310 * @see #getDefaultLogLevel() 311 */ 312 public static void setDefaultLogLevel( final Level value ) 313 { 314 defaultLogLevel = value; 315 } 316 317 /** 318 * Gets the log level of the instance. 319 * 320 * @return The log level of the instance. 321 * 322 * @see #getDefaultLogLevel() 323 * @see #setLogLevel(java.util.logging.Level) 324 * @see #isLoggable(java.util.logging.Level) 325 */ 326 public final Level getLogLevel() 327 { 328 if ( this.logLevel == null ) 329 { 330 this.logLevel = getDefaultLogLevel(); 331 332 if ( this.isLoggable( Level.CONFIG ) ) 333 { 334 this.log( Level.CONFIG, getMessage( "defaultLogLevelInfo", this.logLevel.getLocalizedName() ), null ); 335 } 336 } 337 338 return this.logLevel; 339 } 340 341 /** 342 * Sets the log level of the instance. 343 * 344 * @param value The new log level of the instance or {@code null}. 345 * 346 * @see #getLogLevel() 347 * @see #isLoggable(java.util.logging.Level) 348 */ 349 public final void setLogLevel( final Level value ) 350 { 351 this.logLevel = value; 352 } 353 354 /** 355 * Checks if a message at a given level is provided to the listeners of the instance. 356 * 357 * @param level The level to test. 358 * 359 * @return {@code true}, if messages at {@code level} are provided to the listeners of the instance; 360 * {@code false}, if messages at {@code level} are not provided to the listeners of the instance. 361 * 362 * @throws NullPointerException if {@code level} is {@code null}. 363 * 364 * @see #getLogLevel() 365 * @see #setLogLevel(java.util.logging.Level) 366 * @see #log(java.util.logging.Level, java.lang.String, java.lang.Throwable) 367 */ 368 public boolean isLoggable( final Level level ) 369 { 370 if ( level == null ) 371 { 372 throw new NullPointerException( "level" ); 373 } 374 375 return level.intValue() >= this.getLogLevel().intValue(); 376 } 377 378 /** 379 * Gets the Java package name of a specification. 380 * 381 * @param specification The specification to get the Java package name of. 382 * 383 * @return The Java package name of {@code specification} or {@code null}. 384 * 385 * @throws NullPointerException if {@code specification} is {@code null}. 386 */ 387 public String getJavaPackageName( final Specification specification ) 388 { 389 if ( specification == null ) 390 { 391 throw new NullPointerException( "specification" ); 392 } 393 394 return specification.getClazz() != null ? this.getJavaPackageName( specification.getClazz() ) : null; 395 } 396 397 /** 398 * Gets the Java type name of a specification. 399 * 400 * @param specification The specification to get the Java type name of. 401 * @param qualified {@code true}, to return the fully qualified type name (with package name prepended); 402 * {@code false}, to return the short type name (without package name prepended). 403 * 404 * @return The Java type name of {@code specification} or {@code null}. 405 * 406 * @throws NullPointerException if {@code specification} is {@code null}. 407 */ 408 public String getJavaTypeName( final Specification specification, final boolean qualified ) 409 { 410 if ( specification == null ) 411 { 412 throw new NullPointerException( "specification" ); 413 } 414 415 if ( specification.getClazz() != null ) 416 { 417 final StringBuilder typeName = new StringBuilder( specification.getClazz().length() ); 418 final String javaPackageName = this.getJavaPackageName( specification ); 419 420 if ( qualified && javaPackageName.length() > 0 ) 421 { 422 typeName.append( javaPackageName ).append( '.' ); 423 } 424 425 typeName.append( javaPackageName.length() > 0 426 ? specification.getClazz().substring( javaPackageName.length() + 1 ) 427 : specification.getClazz() ); 428 429 return typeName.toString(); 430 } 431 432 return null; 433 } 434 435 /** 436 * Gets the Java class path location of a specification. 437 * 438 * @param specification The specification to return the Java class path location of. 439 * 440 * @return The Java class path location of {@code specification} or {@code null}. 441 * 442 * @throws NullPointerException if {@code specification} is {@code null}. 443 */ 444 public String getJavaClasspathLocation( final Specification specification ) 445 { 446 if ( specification == null ) 447 { 448 throw new NullPointerException( "specification" ); 449 } 450 451 return specification.getClazz() != null 452 ? ( this.getJavaTypeName( specification, true ) ).replace( '.', '/' ) 453 : null; 454 455 } 456 457 /** 458 * Gets the Java package name of a specification reference. 459 * 460 * @param reference The specification reference to get the Java package name of. 461 * 462 * @return The Java package name of {@code reference} or {@code null}. 463 * 464 * @throws NullPointerException if {@code reference} is {@code null}. 465 */ 466 public String getJavaPackageName( final SpecificationReference reference ) 467 { 468 if ( reference == null ) 469 { 470 throw new NullPointerException( "reference" ); 471 } 472 473 final Specification s = this.getModules().getSpecification( reference.getIdentifier() ); 474 assert s != null : "Specification '" + reference.getIdentifier() + "' not found."; 475 return s.getClazz() != null ? this.getJavaPackageName( s ) : null; 476 } 477 478 /** 479 * Gets the name of a Java type of a given specification reference. 480 * 481 * @param reference The specification reference to get a Java type name of. 482 * @param qualified {@code true}, to return the fully qualified type name (with package name prepended); 483 * {@code false}, to return the short type name (without package name prepended). 484 * 485 * @return The Java type name of {@code reference} or {@code null}. 486 * 487 * @throws NullPointerException if {@code reference} is {@code null}. 488 */ 489 public String getJavaTypeName( final SpecificationReference reference, final boolean qualified ) 490 { 491 if ( reference == null ) 492 { 493 throw new NullPointerException( "reference" ); 494 } 495 496 final Specification s = this.getModules().getSpecification( reference.getIdentifier() ); 497 assert s != null : "Specification '" + reference.getIdentifier() + "' not found."; 498 return s.getClazz() != null ? this.getJavaTypeName( s, qualified ) : null; 499 } 500 501 /** 502 * Gets the Java package name of an implementation. 503 * 504 * @param implementation The implementation to get the Java package name of. 505 * 506 * @return The Java package name of {@code implementation} or {@code null}. 507 * 508 * @throws NullPointerException if {@code implementation} is {@code null}. 509 */ 510 public String getJavaPackageName( final Implementation implementation ) 511 { 512 if ( implementation == null ) 513 { 514 throw new NullPointerException( "implementation" ); 515 } 516 517 return implementation.getClazz() != null ? this.getJavaPackageName( implementation.getClazz() ) : null; 518 } 519 520 /** 521 * Gets the Java type name of an implementation. 522 * 523 * @param implementation The implementation to get the Java type name of. 524 * @param qualified {@code true}, to return the fully qualified type name (with package name prepended); 525 * {@code false}, to return the short type name (without package name prepended). 526 * 527 * @return The Java type name of {@code implementation} or {@code null}. 528 * 529 * @throws NullPointerException if {@code implementation} is {@code null}. 530 */ 531 public String getJavaTypeName( final Implementation implementation, final boolean qualified ) 532 { 533 if ( implementation == null ) 534 { 535 throw new NullPointerException( "implementation" ); 536 } 537 538 if ( implementation.getClazz() != null ) 539 { 540 final StringBuilder typeName = new StringBuilder( implementation.getClazz().length() ); 541 final String javaPackageName = this.getJavaPackageName( implementation ); 542 543 if ( qualified && javaPackageName.length() > 0 ) 544 { 545 typeName.append( javaPackageName ).append( '.' ); 546 } 547 548 typeName.append( javaPackageName.length() > 0 549 ? implementation.getClazz().substring( javaPackageName.length() + 1 ) 550 : implementation.getClazz() ); 551 552 return typeName.toString(); 553 } 554 555 return null; 556 } 557 558 /** 559 * Gets the Java class path location of an implementation. 560 * 561 * @param implementation The implementation to return the Java class path location of. 562 * 563 * @return The Java class path location of {@code implementation} or {@code null}. 564 * 565 * @throws NullPointerException if {@code implementation} is {@code null}. 566 */ 567 public String getJavaClasspathLocation( final Implementation implementation ) 568 { 569 if ( implementation == null ) 570 { 571 throw new NullPointerException( "implementation" ); 572 } 573 574 return implementation.getClazz() != null 575 ? ( this.getJavaTypeName( implementation, true ) ).replace( '.', '/' ) 576 : null; 577 578 } 579 580 /** 581 * Gets a list of names of all Java types an implementation implements. 582 * 583 * @param implementation The implementation to get names of all implemented Java types of. 584 * @param qualified {@code true}, to return the fully qualified type names (with package name prepended); 585 * {@code false}, to return the short type names (without package name prepended). 586 * 587 * @return An unmodifiable list of names of all Java types implemented by {@code implementation}. 588 * 589 * @throws NullPointerException if {@code implementation} is {@code null}. 590 * 591 * @deprecated As of JOMC 1.2, replaced by method {@link #getImplementedJavaTypeNames(org.jomc.model.Implementation, boolean)}. 592 * This method will be removed in version 2.0. 593 */ 594 @Deprecated 595 public List<String> getJavaInterfaceNames( final Implementation implementation, final boolean qualified ) 596 { 597 if ( implementation == null ) 598 { 599 throw new NullPointerException( "implementation" ); 600 } 601 602 return this.getImplementedJavaTypeNames( implementation, qualified ); 603 } 604 605 /** 606 * Gets a list of names of all Java types an implementation implements. 607 * 608 * @param implementation The implementation to get names of all implemented Java types of. 609 * @param qualified {@code true}, to return the fully qualified type names (with package name prepended); 610 * {@code false}, to return the short type names (without package name prepended). 611 * 612 * @return An unmodifiable list of names of all Java types implemented by {@code implementation}. 613 * 614 * @throws NullPointerException if {@code implementation} is {@code null}. 615 * 616 * @since 1.2 617 */ 618 public List<String> getImplementedJavaTypeNames( final Implementation implementation, final boolean qualified ) 619 { 620 if ( implementation == null ) 621 { 622 throw new NullPointerException( "implementation" ); 623 } 624 625 final Specifications specs = this.getModules().getSpecifications( implementation.getIdentifier() ); 626 final List<String> col = new ArrayList<String>( specs == null ? 0 : specs.getSpecification().size() ); 627 628 if ( specs != null ) 629 { 630 for ( int i = 0, s0 = specs.getSpecification().size(); i < s0; i++ ) 631 { 632 final Specification s = specs.getSpecification().get( i ); 633 634 if ( s.getClazz() != null ) 635 { 636 final String typeName = this.getJavaTypeName( s, qualified ); 637 if ( !col.contains( typeName ) ) 638 { 639 col.add( typeName ); 640 } 641 } 642 } 643 } 644 645 return Collections.unmodifiableList( col ); 646 } 647 648 /** 649 * Gets the Java type name of an argument. 650 * 651 * @param argument The argument to get the Java type name of. 652 * 653 * @return The Java type name of {@code argument}. 654 * 655 * @throws NullPointerException if {@code argument} is {@code null}. 656 */ 657 public String getJavaTypeName( final Argument argument ) 658 { 659 if ( argument == null ) 660 { 661 throw new NullPointerException( "argument" ); 662 } 663 664 String javaTypeName = "java.lang.String"; 665 666 if ( argument.getType() == ArgumentType.DATE || argument.getType() == ArgumentType.TIME ) 667 { 668 javaTypeName = "java.util.Date"; 669 } 670 else if ( argument.getType() == ArgumentType.NUMBER ) 671 { 672 javaTypeName = "java.lang.Number"; 673 } 674 675 return javaTypeName; 676 } 677 678 /** 679 * Gets a Java method parameter name of an argument. 680 * 681 * @param argument The argument to get the Java method parameter name of. 682 * 683 * @return The Java method parameter name of {@code argument}. 684 * 685 * @throws NullPointerException if {@code argument} is {@code null}. 686 * 687 * @since 1.2 688 */ 689 public String getJavaMethodParameterName( final Argument argument ) 690 { 691 if ( argument == null ) 692 { 693 throw new NullPointerException( "argument" ); 694 } 695 696 return this.getJavaMethodParameterName( argument.getName() ); 697 } 698 699 /** 700 * Gets the Java type name of a property. 701 * 702 * @param property The property to get the Java type name of. 703 * @param boxify {@code true}, to return the name of the Java wrapper class when the type is a Java primitive type; 704 * {@code false}, to return the exact binary name (unboxed name) of the Java type. 705 * 706 * @return The Java type name of {@code property}. 707 * 708 * @throws NullPointerException if {@code property} is {@code null}. 709 */ 710 public String getJavaTypeName( final Property property, final boolean boxify ) 711 { 712 if ( property == null ) 713 { 714 throw new NullPointerException( "property" ); 715 } 716 717 if ( property.getType() != null ) 718 { 719 final String typeName = property.getType(); 720 721 if ( boxify ) 722 { 723 if ( Boolean.TYPE.getName().equals( typeName ) ) 724 { 725 return Boolean.class.getName(); 726 } 727 if ( Byte.TYPE.getName().equals( typeName ) ) 728 { 729 return Byte.class.getName(); 730 } 731 if ( Character.TYPE.getName().equals( typeName ) ) 732 { 733 return Character.class.getName(); 734 } 735 if ( Double.TYPE.getName().equals( typeName ) ) 736 { 737 return Double.class.getName(); 738 } 739 if ( Float.TYPE.getName().equals( typeName ) ) 740 { 741 return Float.class.getName(); 742 } 743 if ( Integer.TYPE.getName().equals( typeName ) ) 744 { 745 return Integer.class.getName(); 746 } 747 if ( Long.TYPE.getName().equals( typeName ) ) 748 { 749 return Long.class.getName(); 750 } 751 if ( Short.TYPE.getName().equals( typeName ) ) 752 { 753 return Short.class.getName(); 754 } 755 } 756 757 return typeName; 758 } 759 760 return property.getAny() != null ? Object.class.getName() : String.class.getName(); 761 } 762 763 /** 764 * Gets a flag indicating the type of a given property is a Java primitive. 765 * 766 * @param property The property to query. 767 * 768 * @return {@code true}, if the Java type of {@code property} is primitive; {@code false}, if not. 769 * 770 * @throws NullPointerException if {@code property} is {@code null}. 771 */ 772 public boolean isJavaPrimitiveType( final Property property ) 773 { 774 if ( property == null ) 775 { 776 throw new NullPointerException( "property" ); 777 } 778 779 return !this.getJavaTypeName( property, false ).equals( this.getJavaTypeName( property, true ) ); 780 } 781 782 /** 783 * Gets the name of a Java getter method of a given property. 784 * 785 * @param property The property to get a Java getter method name of. 786 * 787 * @return The Java getter method name of {@code property}. 788 * 789 * @throws NullPointerException if {@code property} is {@code null}. 790 * 791 * @see #getJavaIdentifier(java.lang.String, boolean) 792 */ 793 public String getJavaGetterMethodName( final Property property ) 794 { 795 if ( property == null ) 796 { 797 throw new NullPointerException( "property" ); 798 } 799 800 String prefix = "get"; 801 802 final String javaTypeName = this.getJavaTypeName( property, true ); 803 if ( Boolean.class.getName().equals( javaTypeName ) ) 804 { 805 prefix = "is"; 806 } 807 808 return prefix + this.getJavaIdentifier( property.getName(), true ); 809 } 810 811 /** 812 * Gets the name of a Java setter method of a given property. 813 * 814 * @param property The property to get a Java setter method name of. 815 * 816 * @return The Java setter method name of {@code property}. 817 * 818 * @throws NullPointerException if {@code property} is {@code null}. 819 * 820 * @see #getJavaIdentifier(java.lang.String, boolean) 821 * 822 * @since 1.2 823 */ 824 public String getJavaSetterMethodName( final Property property ) 825 { 826 if ( property == null ) 827 { 828 throw new NullPointerException( "property" ); 829 } 830 831 return "set" + this.getJavaIdentifier( property.getName(), true ); 832 } 833 834 /** 835 * Gets a Java method parameter name of a property. 836 * 837 * @param property The property to get the Java method parameter name of. 838 * 839 * @return The Java method parameter name of {@code property}. 840 * 841 * @throws NullPointerException if {@code property} is {@code null}. 842 * 843 * @since 1.2 844 */ 845 public String getJavaMethodParameterName( final Property property ) 846 { 847 if ( property == null ) 848 { 849 throw new NullPointerException( "property" ); 850 } 851 852 return this.getJavaMethodParameterName( property.getName() ); 853 } 854 855 /** 856 * Gets the name of a Java type of a given dependency. 857 * 858 * @param dependency The dependency to get a dependency Java type name of. 859 * 860 * @return The Java type name of {@code dependency} or {@code null}. 861 * 862 * @throws NullPointerException if {@code dependency} is {@code null}. 863 */ 864 public String getJavaTypeName( final Dependency dependency ) 865 { 866 if ( dependency == null ) 867 { 868 throw new NullPointerException( "dependency" ); 869 } 870 871 final Specification s = this.getModules().getSpecification( dependency.getIdentifier() ); 872 873 if ( s != null && s.getClazz() != null ) 874 { 875 final StringBuilder typeName = new StringBuilder( s.getClazz().length() ); 876 typeName.append( this.getJavaTypeName( s, true ) ); 877 if ( s.getMultiplicity() == Multiplicity.MANY && dependency.getImplementationName() == null ) 878 { 879 typeName.append( "[]" ); 880 } 881 882 return typeName.toString(); 883 } 884 885 return null; 886 } 887 888 /** 889 * Gets the name of a Java getter method of a given dependency. 890 * 891 * @param dependency The dependency to get a Java getter method name of. 892 * 893 * @return The Java getter method name of {@code dependency}. 894 * 895 * @throws NullPointerException if {@code dependency} is {@code null}. 896 * 897 * @see #getJavaIdentifier(java.lang.String, boolean) 898 */ 899 public String getJavaGetterMethodName( final Dependency dependency ) 900 { 901 if ( dependency == null ) 902 { 903 throw new NullPointerException( "dependency" ); 904 } 905 906 return "get" + this.getJavaIdentifier( dependency.getName(), true ); 907 } 908 909 /** 910 * Gets the name of a Java setter method of a given dependency. 911 * 912 * @param dependency The dependency to get a Java setter method name of. 913 * 914 * @return The Java setter method name of {@code dependency}. 915 * 916 * @throws NullPointerException if {@code dependency} is {@code null}. 917 * 918 * @see #getJavaIdentifier(java.lang.String, boolean) 919 * 920 * @since 1.2 921 */ 922 public String getJavaSetterMethodName( final Dependency dependency ) 923 { 924 if ( dependency == null ) 925 { 926 throw new NullPointerException( "dependency" ); 927 } 928 929 return "set" + this.getJavaIdentifier( dependency.getName(), true ); 930 } 931 932 /** 933 * Gets a Java method parameter name of a dependency. 934 * 935 * @param dependency The dependency to get the Java method parameter name of. 936 * 937 * @return The Java method parameter name of {@code dependency}. 938 * 939 * @throws NullPointerException if {@code dependency} is {@code null}. 940 * 941 * @since 1.2 942 */ 943 public String getJavaMethodParameterName( final Dependency dependency ) 944 { 945 if ( dependency == null ) 946 { 947 throw new NullPointerException( "dependency" ); 948 } 949 950 return this.getJavaMethodParameterName( dependency.getName() ); 951 } 952 953 /** 954 * Gets the name of a Java getter method of a given message. 955 * 956 * @param message The message to get a Java getter method name of. 957 * 958 * @return The Java getter method name of {@code message}. 959 * 960 * @throws NullPointerException if {@code message} is {@code null}. 961 * 962 * @see #getJavaIdentifier(java.lang.String, boolean) 963 */ 964 public String getJavaGetterMethodName( final Message message ) 965 { 966 if ( message == null ) 967 { 968 throw new NullPointerException( "message" ); 969 } 970 971 return "get" + this.getJavaIdentifier( message.getName(), true ); 972 } 973 974 /** 975 * Gets the name of a Java setter method of a given message. 976 * 977 * @param message The message to get a Java setter method name of. 978 * 979 * @return The Java setter method name of {@code message}. 980 * 981 * @throws NullPointerException if {@code message} is {@code null}. 982 * 983 * @see #getJavaIdentifier(java.lang.String, boolean) 984 * 985 * @since 1.2 986 */ 987 public String getJavaSetterMethodName( final Message message ) 988 { 989 if ( message == null ) 990 { 991 throw new NullPointerException( "message" ); 992 } 993 994 return "set" + this.getJavaIdentifier( message.getName(), true ); 995 } 996 997 /** 998 * Gets a Java method parameter name of a message. 999 * 1000 * @param message The message to get the Java method parameter name of. 1001 * 1002 * @return The Java method parameter name of {@code message}. 1003 * 1004 * @throws NullPointerException if {@code message} is {@code null}. 1005 * 1006 * @since 1.2 1007 */ 1008 public String getJavaMethodParameterName( final Message message ) 1009 { 1010 if ( message == null ) 1011 { 1012 throw new NullPointerException( "message" ); 1013 } 1014 1015 return this.getJavaMethodParameterName( message.getName() ); 1016 } 1017 1018 /** 1019 * Gets the Java modifier name of a dependency of a given implementation. 1020 * 1021 * @param implementation The implementation declaring the dependency to get a Java modifier name of. 1022 * @param dependency The dependency to get a Java modifier name of. 1023 * 1024 * @return The Java modifier name of {@code dependency} of {@code implementation}. 1025 * 1026 * @throws NullPointerException if {@code implementation} or {@code dependency} is {@code null}. 1027 */ 1028 public String getJavaModifierName( final Implementation implementation, final Dependency dependency ) 1029 { 1030 if ( implementation == null ) 1031 { 1032 throw new NullPointerException( "implementation" ); 1033 } 1034 if ( dependency == null ) 1035 { 1036 throw new NullPointerException( "dependency" ); 1037 } 1038 1039 return "private"; 1040 } 1041 1042 /** 1043 * Gets the Java modifier name of a message of a given implementation. 1044 * 1045 * @param implementation The implementation declaring the message to get a Java modifier name of. 1046 * @param message The message to get a Java modifier name of. 1047 * 1048 * @return The Java modifier name of {@code message} of {@code implementation}. 1049 * 1050 * @throws NullPointerException if {@code implementation} or {@code message} is {@code null}. 1051 */ 1052 public String getJavaModifierName( final Implementation implementation, final Message message ) 1053 { 1054 if ( implementation == null ) 1055 { 1056 throw new NullPointerException( "implementation" ); 1057 } 1058 if ( message == null ) 1059 { 1060 throw new NullPointerException( "message" ); 1061 } 1062 1063 return "private"; 1064 } 1065 1066 /** 1067 * Gets the Java modifier name of a property of a given implementation. 1068 * 1069 * @param implementation The implementation declaring the property to get a Java modifier name of. 1070 * @param property The property to get a Java modifier name of. 1071 * 1072 * @return The Java modifier name of {@code property} of {@code implementation}. 1073 * 1074 * @throws NullPointerException if {@code implementation} or {@code property} is {@code null}. 1075 */ 1076 public String getJavaModifierName( final Implementation implementation, final Property property ) 1077 { 1078 if ( implementation == null ) 1079 { 1080 throw new NullPointerException( "implementation" ); 1081 } 1082 if ( property == null ) 1083 { 1084 throw new NullPointerException( "property" ); 1085 } 1086 1087 String modifier = "private"; 1088 final Properties specified = this.getModules().getSpecifiedProperties( implementation.getIdentifier() ); 1089 1090 if ( specified != null && specified.getProperty( property.getName() ) != null ) 1091 { 1092 modifier = "public"; 1093 } 1094 1095 return modifier; 1096 } 1097 1098 /** 1099 * Formats a text to a Javadoc comment. 1100 * 1101 * @param text The text to format to a Javadoc comment. 1102 * @param indentationLevel The indentation level of the comment. 1103 * @param linePrefix The text to prepend lines with. 1104 * 1105 * @return {@code text} formatted to a Javadoc comment. 1106 * 1107 * @throws NullPointerException if {@code text} or {@code linePrefix} is {@code null}. 1108 * @throws IllegalArgumentException if {@code indentationLevel} is negative. 1109 */ 1110 public String getJavadocComment( final Text text, final int indentationLevel, final String linePrefix ) 1111 { 1112 if ( text == null ) 1113 { 1114 throw new NullPointerException( "text" ); 1115 } 1116 if ( linePrefix == null ) 1117 { 1118 throw new NullPointerException( "linePrefix" ); 1119 } 1120 if ( indentationLevel < 0 ) 1121 { 1122 throw new IllegalArgumentException( Integer.toString( indentationLevel ) ); 1123 } 1124 1125 BufferedReader reader = null; 1126 boolean suppressExceptionOnClose = true; 1127 1128 try 1129 { 1130 String javadoc = ""; 1131 1132 if ( text.getValue() != null ) 1133 { 1134 final String indent = this.getIndentation( indentationLevel ); 1135 reader = new BufferedReader( new StringReader( text.getValue() ) ); 1136 final StringBuilder builder = new StringBuilder( text.getValue().length() ); 1137 1138 String line; 1139 while ( ( line = reader.readLine() ) != null ) 1140 { 1141 builder.append( this.getLineSeparator() ).append( indent ).append( linePrefix ). 1142 append( line.replaceAll( "\\/\\*\\*", "/*" ).replaceAll( "\\*/", "/" ) ); 1143 1144 } 1145 1146 if ( builder.length() > 0 ) 1147 { 1148 javadoc = 1149 builder.substring( this.getLineSeparator().length() + indent.length() + linePrefix.length() ); 1150 1151 if ( !new MimeType( text.getType() ).match( "text/html" ) ) 1152 { 1153 javadoc = StringEscapeUtils.escapeHtml( javadoc ); 1154 } 1155 } 1156 } 1157 1158 suppressExceptionOnClose = false; 1159 return javadoc; 1160 } 1161 catch ( final MimeTypeParseException e ) 1162 { 1163 throw new AssertionError( e ); 1164 } 1165 catch ( final IOException e ) 1166 { 1167 throw new AssertionError( e ); 1168 } 1169 finally 1170 { 1171 try 1172 { 1173 if ( reader != null ) 1174 { 1175 reader.close(); 1176 } 1177 } 1178 catch ( final IOException e ) 1179 { 1180 if ( suppressExceptionOnClose ) 1181 { 1182 this.log( Level.SEVERE, getMessage( e ), e ); 1183 } 1184 else 1185 { 1186 throw new AssertionError( e ); 1187 } 1188 } 1189 } 1190 } 1191 1192 /** 1193 * Formats a text from a list of texts to a Javadoc comment. 1194 * 1195 * @param texts The list of texts to format to a Javadoc comment. 1196 * @param indentationLevel The indentation level of the comment. 1197 * @param linePrefix The text to prepend lines with. 1198 * 1199 * @return The text corresponding to the locale of the instance from the list of texts formatted to a Javadoc 1200 * comment. 1201 * 1202 * @throws NullPointerException if {@code texts} or {@code linePrefix} is {@code null}. 1203 * @throws IllegalArgumentException if {@code indentationLevel} is negative. 1204 * 1205 * @see #getLocale() 1206 * 1207 * @since 1.2 1208 */ 1209 public String getJavadocComment( final Texts texts, final int indentationLevel, final String linePrefix ) 1210 { 1211 if ( texts == null ) 1212 { 1213 throw new NullPointerException( "texts" ); 1214 } 1215 if ( linePrefix == null ) 1216 { 1217 throw new NullPointerException( "linePrefix" ); 1218 } 1219 if ( indentationLevel < 0 ) 1220 { 1221 throw new IllegalArgumentException( Integer.toString( indentationLevel ) ); 1222 } 1223 1224 return this.getJavadocComment( texts.getText( this.getLocale().getLanguage() ), indentationLevel, linePrefix ); 1225 } 1226 1227 /** 1228 * Formats a string to a Java string with unicode escapes. 1229 * 1230 * @param str The string to format to a Java string or {@code null}. 1231 * 1232 * @return {@code str} formatted to a Java string or {@code null}. 1233 * 1234 * @see StringEscapeUtils#escapeJava(java.lang.String) 1235 */ 1236 public String getJavaString( final String str ) 1237 { 1238 return StringEscapeUtils.escapeJava( str ); 1239 } 1240 1241 /** 1242 * Formats a string to a Java identifier. 1243 * 1244 * @param str The string to format or {@code null}. 1245 * @param capitalize {@code true}, to return an identifier with the first character upper cased; {@code false}, to 1246 * return an identifier with the first character lower cased. 1247 * 1248 * @return {@code str} formatted to a Java identifier or {@code null}. 1249 * 1250 * @since 1.2 1251 */ 1252 public String getJavaIdentifier( final String str, final boolean capitalize ) 1253 { 1254 String identifier = null; 1255 1256 if ( str != null ) 1257 { 1258 final int len = str.length(); 1259 final StringBuilder builder = new StringBuilder( len ); 1260 boolean uc = capitalize; 1261 1262 for ( int i = 0; i < len; i++ ) 1263 { 1264 final char c = str.charAt( i ); 1265 1266 if ( builder.length() > 0 ) 1267 { 1268 if ( Character.isJavaIdentifierPart( c ) ) 1269 { 1270 builder.append( uc ? Character.toUpperCase( c ) : c ); 1271 uc = false; 1272 } 1273 else 1274 { 1275 uc = true; 1276 } 1277 } 1278 else 1279 { 1280 if ( Character.isJavaIdentifierStart( c ) ) 1281 { 1282 builder.append( uc ? Character.toUpperCase( c ) : Character.toLowerCase( c ) ); 1283 uc = false; 1284 } 1285 else 1286 { 1287 uc = capitalize; 1288 } 1289 } 1290 } 1291 1292 identifier = builder.toString(); 1293 1294 if ( identifier.length() <= 0 && this.isLoggable( Level.WARNING ) ) 1295 { 1296 this.log( Level.WARNING, getMessage( "invalidJavaIdentifier", str ), null ); 1297 } 1298 } 1299 1300 return identifier; 1301 } 1302 1303 /** 1304 * Formats a string to a Java method parameter name. 1305 * 1306 * @param str The string to format or {@code null}. 1307 * 1308 * @return {@code str} formatted to a Java method parameter name or {@code null}. 1309 * 1310 * @since 1.3 1311 */ 1312 private String getJavaMethodParameterName( final String str ) 1313 { 1314 String methodParameterName = null; 1315 1316 if ( str != null ) 1317 { 1318 final int len = str.length(); 1319 final StringBuilder builder = new StringBuilder( len ); 1320 boolean uc = false; 1321 1322 for ( int i = 0; i < len; i++ ) 1323 { 1324 final char c = str.charAt( i ); 1325 1326 if ( builder.length() > 0 ) 1327 { 1328 if ( Character.isJavaIdentifierPart( c ) ) 1329 { 1330 builder.append( uc ? Character.toUpperCase( c ) : c ); 1331 uc = false; 1332 } 1333 else 1334 { 1335 uc = true; 1336 } 1337 } 1338 else if ( Character.isJavaIdentifierStart( c ) ) 1339 { 1340 builder.append( Character.toLowerCase( c ) ); 1341 } 1342 } 1343 1344 methodParameterName = builder.toString(); 1345 1346 if ( methodParameterName.length() <= 0 && this.isLoggable( Level.WARNING ) ) 1347 { 1348 this.log( Level.WARNING, getMessage( "invalidJavaMethodParameterName", str ), null ); 1349 } 1350 1351 if ( this.getJavaKeywords().contains( methodParameterName ) ) 1352 { 1353 methodParameterName = "_" + methodParameterName; 1354 } 1355 } 1356 1357 return methodParameterName; 1358 } 1359 1360 /** 1361 * Gets a flag indicating the class of a given specification is located in the Java default package. 1362 * 1363 * @param specification The specification to query. 1364 * 1365 * @return {@code true}, if the class of {@code specification} is located in the Java default package; 1366 * {@code false}, else. 1367 * 1368 * @throws NullPointerException if {@code specification} is {@code null}. 1369 */ 1370 public boolean isJavaDefaultPackage( final Specification specification ) 1371 { 1372 if ( specification == null ) 1373 { 1374 throw new NullPointerException( "specification" ); 1375 } 1376 1377 return specification.getClazz() != null && this.getJavaPackageName( specification ).length() == 0; 1378 } 1379 1380 /** 1381 * Gets a flag indicating the class of a given implementation is located in the Java default package. 1382 * 1383 * @param implementation The implementation to query. 1384 * 1385 * @return {@code true}, if the class of {@code implementation} is located in the Java default package; 1386 * {@code false}, else. 1387 * 1388 * @throws NullPointerException if {@code implementation} is {@code null}. 1389 */ 1390 public boolean isJavaDefaultPackage( final Implementation implementation ) 1391 { 1392 if ( implementation == null ) 1393 { 1394 throw new NullPointerException( "implementation" ); 1395 } 1396 1397 return implementation.getClazz() != null && this.getJavaPackageName( implementation ).length() == 0; 1398 } 1399 1400 /** 1401 * Formats a string to a HTML string with HTML entities. 1402 * 1403 * @param str The string to format to a HTML string with HTML entities or {@code null}. 1404 * 1405 * @return {@code str} formatted to a HTML string with HTML entities or {@code null}. 1406 * 1407 * @see StringEscapeUtils#escapeHtml(java.lang.String) 1408 * 1409 * @since 1.2 1410 */ 1411 public String getHtmlString( final String str ) 1412 { 1413 return StringEscapeUtils.escapeHtml( str ); 1414 } 1415 1416 /** 1417 * Formats a string to a XML string with XML entities. 1418 * 1419 * @param str The string to format to a XML string with XML entities or {@code null}. 1420 * 1421 * @return {@code str} formatted to a XML string with XML entities or {@code null}. 1422 * 1423 * @see StringEscapeUtils#escapeXml(java.lang.String) 1424 * 1425 * @since 1.2 1426 */ 1427 public String getXmlString( final String str ) 1428 { 1429 return StringEscapeUtils.escapeXml( str ); 1430 } 1431 1432 /** 1433 * Formats a string to a JavaScript string applying JavaScript string rules. 1434 * 1435 * @param str The string to format to a JavaScript string by applying JavaScript string rules or {@code null}. 1436 * 1437 * @return {@code str} formatted to a JavaScript string with JavaScript string rules applied or {@code null}. 1438 * 1439 * @see StringEscapeUtils#escapeJavaScript(java.lang.String) 1440 * 1441 * @since 1.2 1442 */ 1443 public String getJavaScriptString( final String str ) 1444 { 1445 return StringEscapeUtils.escapeJavaScript( str ); 1446 } 1447 1448 /** 1449 * Formats a string to a SQL string. 1450 * 1451 * @param str The string to format to a SQL string or {@code null}. 1452 * 1453 * @return {@code str} formatted to a SQL string or {@code null}. 1454 * 1455 * @see StringEscapeUtils#escapeSql(java.lang.String) 1456 * 1457 * @since 1.2 1458 */ 1459 public String getSqlString( final String str ) 1460 { 1461 return StringEscapeUtils.escapeSql( str ); 1462 } 1463 1464 /** 1465 * Formats a string to a CSV string. 1466 * 1467 * @param str The string to format to a CSV string or {@code null}. 1468 * 1469 * @return {@code str} formatted to a CSV string or {@code null}. 1470 * 1471 * @see StringEscapeUtils#escapeCsv(java.lang.String) 1472 * 1473 * @since 1.2 1474 */ 1475 public String getCsvString( final String str ) 1476 { 1477 return StringEscapeUtils.escapeCsv( str ); 1478 } 1479 1480 /** 1481 * Formats a {@code Boolean} to a string. 1482 * 1483 * @param b The {@code Boolean} to format to a string or {@code null}. 1484 * 1485 * @return {@code b} formatted to a string. 1486 * 1487 * @see #getLocale() 1488 * 1489 * @since 1.2 1490 */ 1491 public String getBooleanString( final Boolean b ) 1492 { 1493 final MessageFormat messageFormat = new MessageFormat( ResourceBundle.getBundle( 1494 JomcTool.class.getName().replace( '.', '/' ), this.getLocale() ). 1495 getString( b ? "booleanStringTrue" : "booleanStringFalse" ), this.getLocale() ); 1496 1497 return messageFormat.format( null ); 1498 } 1499 1500 /** 1501 * Gets the display language of a given language code. 1502 * 1503 * @param language The language code to get the display language of. 1504 * 1505 * @return The display language of {@code language}. 1506 * 1507 * @throws NullPointerException if {@code language} is {@code null}. 1508 */ 1509 public String getDisplayLanguage( final String language ) 1510 { 1511 if ( language == null ) 1512 { 1513 throw new NullPointerException( "language" ); 1514 } 1515 1516 final Locale l = new Locale( language ); 1517 return l.getDisplayLanguage( l ); 1518 } 1519 1520 /** 1521 * Formats a calendar instance to a string. 1522 * 1523 * @param calendar The calendar to format to a string. 1524 * 1525 * @return The date of {@code calendar} formatted using a short format style pattern. 1526 * 1527 * @throws NullPointerException if {@code calendar} is {@code null}. 1528 * 1529 * @see DateFormat#SHORT 1530 */ 1531 public String getShortDate( final Calendar calendar ) 1532 { 1533 if ( calendar == null ) 1534 { 1535 throw new NullPointerException( "calendar" ); 1536 } 1537 1538 return DateFormat.getDateInstance( DateFormat.SHORT, this.getLocale() ).format( calendar.getTime() ); 1539 } 1540 1541 /** 1542 * Formats a calendar instance to a string. 1543 * 1544 * @param calendar The calendar to format to a string. 1545 * 1546 * @return The date of {@code calendar} formatted using a medium format style pattern. 1547 * 1548 * @throws NullPointerException if {@code calendar} is {@code null}. 1549 * 1550 * @see DateFormat#MEDIUM 1551 * 1552 * @since 1.2 1553 */ 1554 public String getMediumDate( final Calendar calendar ) 1555 { 1556 if ( calendar == null ) 1557 { 1558 throw new NullPointerException( "calendar" ); 1559 } 1560 1561 return DateFormat.getDateInstance( DateFormat.MEDIUM, this.getLocale() ).format( calendar.getTime() ); 1562 } 1563 1564 /** 1565 * Formats a calendar instance to a string. 1566 * 1567 * @param calendar The calendar to format to a string. 1568 * 1569 * @return The date of {@code calendar} formatted using a long format style pattern. 1570 * 1571 * @throws NullPointerException if {@code calendar} is {@code null}. 1572 * 1573 * @see DateFormat#LONG 1574 */ 1575 public String getLongDate( final Calendar calendar ) 1576 { 1577 if ( calendar == null ) 1578 { 1579 throw new NullPointerException( "calendar" ); 1580 } 1581 1582 return DateFormat.getDateInstance( DateFormat.LONG, this.getLocale() ).format( calendar.getTime() ); 1583 } 1584 1585 /** 1586 * Formats a calendar instance to a string. 1587 * 1588 * @param calendar The calendar to format to a string. 1589 * 1590 * @return The date of {@code calendar} formatted using an ISO-8601 format style. 1591 * 1592 * @throws NullPointerException if {@code calendar} is {@code null}. 1593 * 1594 * @see SimpleDateFormat yyyy-DDD 1595 * 1596 * @since 1.2 1597 */ 1598 public String getIsoDate( final Calendar calendar ) 1599 { 1600 if ( calendar == null ) 1601 { 1602 throw new NullPointerException( "calendar" ); 1603 } 1604 1605 return new SimpleDateFormat( "yyyy-DDD", this.getLocale() ).format( calendar.getTime() ); 1606 } 1607 1608 /** 1609 * Formats a calendar instance to a string. 1610 * 1611 * @param calendar The calendar to format to a string. 1612 * 1613 * @return The time of {@code calendar} formatted using a short format style pattern. 1614 * 1615 * @throws NullPointerException if {@code calendar} is {@code null}. 1616 * 1617 * @see DateFormat#SHORT 1618 */ 1619 public String getShortTime( final Calendar calendar ) 1620 { 1621 if ( calendar == null ) 1622 { 1623 throw new NullPointerException( "calendar" ); 1624 } 1625 1626 return DateFormat.getTimeInstance( DateFormat.SHORT, this.getLocale() ).format( calendar.getTime() ); 1627 } 1628 1629 /** 1630 * Formats a calendar instance to a string. 1631 * 1632 * @param calendar The calendar to format to a string. 1633 * 1634 * @return The time of {@code calendar} formatted using a medium format style pattern. 1635 * 1636 * @throws NullPointerException if {@code calendar} is {@code null}. 1637 * 1638 * @see DateFormat#MEDIUM 1639 * 1640 * @since 1.2 1641 */ 1642 public String getMediumTime( final Calendar calendar ) 1643 { 1644 if ( calendar == null ) 1645 { 1646 throw new NullPointerException( "calendar" ); 1647 } 1648 1649 return DateFormat.getTimeInstance( DateFormat.MEDIUM, this.getLocale() ).format( calendar.getTime() ); 1650 } 1651 1652 /** 1653 * Formats a calendar instance to a string. 1654 * 1655 * @param calendar The calendar to format to a string. 1656 * 1657 * @return The time of {@code calendar} formatted using a long format style pattern. 1658 * 1659 * @throws NullPointerException if {@code calendar} is {@code null}. 1660 * 1661 * @see DateFormat#LONG 1662 */ 1663 public String getLongTime( final Calendar calendar ) 1664 { 1665 if ( calendar == null ) 1666 { 1667 throw new NullPointerException( "calendar" ); 1668 } 1669 1670 return DateFormat.getTimeInstance( DateFormat.LONG, this.getLocale() ).format( calendar.getTime() ); 1671 } 1672 1673 /** 1674 * Formats a calendar instance to a string. 1675 * 1676 * @param calendar The calendar to format to a string. 1677 * 1678 * @return The time of {@code calendar} formatted using an ISO-8601 format style. 1679 * 1680 * @throws NullPointerException if {@code calendar} is {@code null}. 1681 * 1682 * @see SimpleDateFormat HH:mm 1683 * 1684 * @since 1.2 1685 */ 1686 public String getIsoTime( final Calendar calendar ) 1687 { 1688 if ( calendar == null ) 1689 { 1690 throw new NullPointerException( "calendar" ); 1691 } 1692 1693 return new SimpleDateFormat( "HH:mm", this.getLocale() ).format( calendar.getTime() ); 1694 } 1695 1696 /** 1697 * Formats a calendar instance to a string. 1698 * 1699 * @param calendar The calendar to format to a string. 1700 * 1701 * @return The date and time of {@code calendar} formatted using a short format style pattern. 1702 * 1703 * @throws NullPointerException if {@code calendar} is {@code null}. 1704 * 1705 * @see DateFormat#SHORT 1706 */ 1707 public String getShortDateTime( final Calendar calendar ) 1708 { 1709 if ( calendar == null ) 1710 { 1711 throw new NullPointerException( "calendar" ); 1712 } 1713 1714 return DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT, this.getLocale() ). 1715 format( calendar.getTime() ); 1716 1717 } 1718 1719 /** 1720 * Formats a calendar instance to a string. 1721 * 1722 * @param calendar The calendar to format to a string. 1723 * 1724 * @return The date and time of {@code calendar} formatted using a medium format style pattern. 1725 * 1726 * @throws NullPointerException if {@code calendar} is {@code null}. 1727 * 1728 * @see DateFormat#MEDIUM 1729 * 1730 * @since 1.2 1731 */ 1732 public String getMediumDateTime( final Calendar calendar ) 1733 { 1734 if ( calendar == null ) 1735 { 1736 throw new NullPointerException( "calendar" ); 1737 } 1738 1739 return DateFormat.getDateTimeInstance( DateFormat.MEDIUM, DateFormat.MEDIUM, this.getLocale() ). 1740 format( calendar.getTime() ); 1741 1742 } 1743 1744 /** 1745 * Formats a calendar instance to a string. 1746 * 1747 * @param calendar The calendar to format to a string. 1748 * 1749 * @return The date and time of {@code calendar} formatted using a long format style pattern. 1750 * 1751 * @throws NullPointerException if {@code calendar} is {@code null}. 1752 * 1753 * @see DateFormat#LONG 1754 */ 1755 public String getLongDateTime( final Calendar calendar ) 1756 { 1757 if ( calendar == null ) 1758 { 1759 throw new NullPointerException( "calendar" ); 1760 } 1761 1762 return DateFormat.getDateTimeInstance( DateFormat.LONG, DateFormat.LONG, this.getLocale() ). 1763 format( calendar.getTime() ); 1764 1765 } 1766 1767 /** 1768 * Formats a calendar instance to a string. 1769 * 1770 * @param calendar The calendar to format to a string. 1771 * 1772 * @return The date and time of {@code calendar} formatted using a ISO-8601 format style. 1773 * 1774 * @throws NullPointerException if {@code calendar} is {@code null}. 1775 * 1776 * @see SimpleDateFormat yyyy-MM-dd'T'HH:mm:ssZ 1777 * 1778 * @since 1.2 1779 */ 1780 public String getIsoDateTime( final Calendar calendar ) 1781 { 1782 if ( calendar == null ) 1783 { 1784 throw new NullPointerException( "calendar" ); 1785 } 1786 1787 // JDK: As of JDK 7, "yyyy-MM-dd'T'HH:mm:ssXXX". 1788 return new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ssZ", this.getLocale() ).format( calendar.getTime() ); 1789 } 1790 1791 /** 1792 * Gets a string describing the range of years for given calendars. 1793 * 1794 * @param start The start of the range. 1795 * @param end The end of the range. 1796 * 1797 * @return Formatted range of the years of {@code start} and {@code end} (e.g. {@code "start - end"}). 1798 * 1799 * @throws NullPointerException if {@code start} or {@code end} is {@code null}. 1800 */ 1801 public String getYears( final Calendar start, final Calendar end ) 1802 { 1803 if ( start == null ) 1804 { 1805 throw new NullPointerException( "start" ); 1806 } 1807 if ( end == null ) 1808 { 1809 throw new NullPointerException( "end" ); 1810 } 1811 1812 final Format yearFormat = new SimpleDateFormat( "yyyy", this.getLocale() ); 1813 final int s = start.get( Calendar.YEAR ); 1814 final int e = end.get( Calendar.YEAR ); 1815 final StringBuilder years = new StringBuilder(); 1816 1817 if ( s != e ) 1818 { 1819 if ( s < e ) 1820 { 1821 years.append( yearFormat.format( start.getTime() ) ).append( " - " ). 1822 append( yearFormat.format( end.getTime() ) ); 1823 1824 } 1825 else 1826 { 1827 years.append( yearFormat.format( end.getTime() ) ).append( " - " ). 1828 append( yearFormat.format( start.getTime() ) ); 1829 1830 } 1831 } 1832 else 1833 { 1834 years.append( yearFormat.format( start.getTime() ) ); 1835 } 1836 1837 return years.toString(); 1838 } 1839 1840 /** 1841 * Gets the model of the instance. 1842 * 1843 * @return The model of the instance. 1844 * 1845 * @see #getModules() 1846 * @see #setModel(org.jomc.modlet.Model) 1847 */ 1848 public final Model getModel() 1849 { 1850 if ( this.model == null ) 1851 { 1852 this.model = new Model(); 1853 this.model.setIdentifier( ModelObject.MODEL_PUBLIC_ID ); 1854 } 1855 1856 return this.model; 1857 } 1858 1859 /** 1860 * Sets the model of the instance. 1861 * 1862 * @param value The new model of the instance or {@code null}. 1863 * 1864 * @see #getModel() 1865 */ 1866 public final void setModel( final Model value ) 1867 { 1868 this.model = value; 1869 } 1870 1871 /** 1872 * Gets the modules of the instance. 1873 * 1874 * @return The modules of the instance. 1875 * 1876 * @see #getModel() 1877 * @see #setModel(org.jomc.modlet.Model) 1878 * 1879 * @deprecated As of JOMC 1.2, please use method {@link #getModel()} and {@link ModelHelper#getModules(org.jomc.modlet.Model)}. 1880 * This method will be removed in version 2.0. 1881 */ 1882 @Deprecated 1883 public Modules getModules() 1884 { 1885 Modules modules = ModelHelper.getModules( this.getModel() ); 1886 1887 if ( modules == null ) 1888 { 1889 modules = new Modules(); 1890 ModelHelper.setModules( this.getModel(), modules ); 1891 } 1892 1893 return modules; 1894 } 1895 1896 /** 1897 * Gets the {@code VelocityEngine} of the instance. 1898 * 1899 * @return The {@code VelocityEngine} of the instance. 1900 * 1901 * @throws IOException if initializing a new velocity engine fails. 1902 * 1903 * @see #setVelocityEngine(org.apache.velocity.app.VelocityEngine) 1904 */ 1905 public final VelocityEngine getVelocityEngine() throws IOException 1906 { 1907 if ( this.velocityEngine == null ) 1908 { 1909 /** {@code LogChute} logging to the listeners of the tool. */ 1910 class JomcLogChute implements LogChute 1911 { 1912 1913 JomcLogChute() 1914 { 1915 super(); 1916 } 1917 1918 public void init( final RuntimeServices runtimeServices ) throws Exception 1919 { 1920 } 1921 1922 public void log( final int level, final String message ) 1923 { 1924 this.log( level, message, null ); 1925 } 1926 1927 public void log( final int level, final String message, final Throwable throwable ) 1928 { 1929 JomcTool.this.log( Level.FINEST, message, throwable ); 1930 } 1931 1932 public boolean isLevelEnabled( final int level ) 1933 { 1934 return isLoggable( Level.FINEST ); 1935 } 1936 1937 } 1938 1939 final VelocityEngine engine = new VelocityEngine(); 1940 engine.setProperty( RuntimeConstants.RUNTIME_REFERENCES_STRICT, Boolean.TRUE.toString() ); 1941 engine.setProperty( RuntimeConstants.VM_ARGUMENTS_STRICT, Boolean.TRUE.toString() ); 1942 engine.setProperty( RuntimeConstants.STRICT_MATH, Boolean.TRUE.toString() ); 1943 engine.setProperty( RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new JomcLogChute() ); 1944 1945 engine.setProperty( RuntimeConstants.RESOURCE_LOADER, "class" ); 1946 engine.setProperty( "class.resource.loader.class", ClasspathResourceLoader.class.getName() ); 1947 engine.setProperty( "class.resource.loader.cache", Boolean.TRUE.toString() ); 1948 1949 if ( this.getTemplateLocation() != null ) 1950 { 1951 engine.setProperty( RuntimeConstants.RESOURCE_LOADER, "class,url" ); 1952 engine.setProperty( "url.resource.loader.class", URLResourceLoader.class.getName() ); 1953 engine.setProperty( "url.resource.loader.cache", Boolean.TRUE.toString() ); 1954 engine.setProperty( "url.resource.loader.root", this.getTemplateLocation().toExternalForm() ); 1955 engine.setProperty( "url.resource.loader.timeout", Integer.toString( 60000 ) ); 1956 } 1957 1958 this.velocityEngine = engine; 1959 } 1960 1961 return this.velocityEngine; 1962 } 1963 1964 /** 1965 * Sets the {@code VelocityEngine} of the instance. 1966 * 1967 * @param value The new {@code VelocityEngine} of the instance or {@code null}. 1968 * 1969 * @see #getVelocityEngine() 1970 */ 1971 public final void setVelocityEngine( final VelocityEngine value ) 1972 { 1973 this.velocityEngine = value; 1974 } 1975 1976 /** 1977 * Gets a new velocity context used for merging templates. 1978 * 1979 * @return A new velocity context used for merging templates. 1980 * 1981 * @see #getTemplateParameters() 1982 */ 1983 public VelocityContext getVelocityContext() 1984 { 1985 final Calendar now = Calendar.getInstance(); 1986 final VelocityContext ctx = new VelocityContext( Collections.synchronizedMap( 1987 new HashMap<String, Object>( this.getTemplateParameters() ) ) ); 1988 1989 this.mergeTemplateProfileProperties( this.getTemplateProfile(), this.getLocale().getLanguage(), ctx ); 1990 this.mergeTemplateProfileProperties( this.getTemplateProfile(), null, ctx ); 1991 this.mergeTemplateProfileProperties( getDefaultTemplateProfile(), this.getLocale().getLanguage(), ctx ); 1992 this.mergeTemplateProfileProperties( getDefaultTemplateProfile(), null, ctx ); 1993 1994 this.getModules(); // Initialization prior to cloning. 1995 final Model clonedModel = this.getModel().clone(); 1996 final Modules clonedModules = ModelHelper.getModules( clonedModel ); 1997 assert clonedModules != null : "Unexpected missing modules for model '" + clonedModel.getIdentifier() + "'."; 1998 1999 ctx.put( "model", clonedModel ); 2000 ctx.put( "modules", clonedModules ); 2001 ctx.put( "imodel", new InheritanceModel( this.getModules() ) ); 2002 ctx.put( "tool", this ); 2003 ctx.put( "toolName", this.getClass().getName() ); 2004 ctx.put( "toolVersion", getMessage( "projectVersion" ) ); 2005 ctx.put( "toolUrl", getMessage( "projectUrl" ) ); 2006 ctx.put( "calendar", now.getTime() ); 2007 2008 // JDK: As of JDK 7, "yyyy-MM-dd'T'HH:mm:ss.SSSXXX". 2009 ctx.put( "now", 2010 new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ", this.getLocale() ).format( now.getTime() ) ); 2011 2012 ctx.put( "year", new SimpleDateFormat( "yyyy", this.getLocale() ).format( now.getTime() ) ); 2013 ctx.put( "month", new SimpleDateFormat( "MM", this.getLocale() ).format( now.getTime() ) ); 2014 ctx.put( "day", new SimpleDateFormat( "dd", this.getLocale() ).format( now.getTime() ) ); 2015 ctx.put( "hour", new SimpleDateFormat( "HH", this.getLocale() ).format( now.getTime() ) ); 2016 ctx.put( "minute", new SimpleDateFormat( "mm", this.getLocale() ).format( now.getTime() ) ); 2017 ctx.put( "second", new SimpleDateFormat( "ss", this.getLocale() ).format( now.getTime() ) ); 2018 ctx.put( "timezone", new SimpleDateFormat( "Z", this.getLocale() ).format( now.getTime() ) ); 2019 ctx.put( "shortDate", this.getShortDate( now ) ); 2020 ctx.put( "mediumDate", this.getMediumDate( now ) ); 2021 ctx.put( "longDate", this.getLongDate( now ) ); 2022 ctx.put( "isoDate", this.getIsoDate( now ) ); 2023 ctx.put( "shortTime", this.getShortTime( now ) ); 2024 ctx.put( "mediumTime", this.getMediumTime( now ) ); 2025 ctx.put( "longTime", this.getLongTime( now ) ); 2026 ctx.put( "isoTime", this.getIsoTime( now ) ); 2027 ctx.put( "shortDateTime", this.getShortDateTime( now ) ); 2028 ctx.put( "mediumDateTime", this.getMediumDateTime( now ) ); 2029 ctx.put( "longDateTime", this.getLongDateTime( now ) ); 2030 ctx.put( "isoDateTime", this.getIsoDateTime( now ) ); 2031 2032 return ctx; 2033 } 2034 2035 /** 2036 * Gets the template parameters of the instance. 2037 * <p>This accessor method returns a reference to the live map, not a snapshot. Therefore any modification you make 2038 * to the returned map will be present inside the object. This is why there is no {@code set} method for the 2039 * template parameters property.</p> 2040 * 2041 * @return The template parameters of the instance. 2042 * 2043 * @see #getVelocityContext() 2044 * 2045 * @since 1.2 2046 */ 2047 public final Map<String, Object> getTemplateParameters() 2048 { 2049 if ( this.templateParameters == null ) 2050 { 2051 this.templateParameters = new HashMap<String, Object>(); 2052 } 2053 2054 return this.templateParameters; 2055 } 2056 2057 /** 2058 * Gets the location to search for templates in addition to searching the class path. 2059 * 2060 * @return The location to search for templates in addition to searching the class path or {@code null}. 2061 * 2062 * @see #setTemplateLocation(java.net.URL) 2063 * 2064 * @since 1.2 2065 */ 2066 public final URL getTemplateLocation() 2067 { 2068 return this.templateLocation; 2069 } 2070 2071 /** 2072 * Sets the location to search for templates in addition to searching the class path. 2073 * 2074 * @param value The new location to search for templates in addition to searching the class path or {@code null}. 2075 * 2076 * @see #getTemplateLocation() 2077 * 2078 * @since 1.2 2079 */ 2080 public final void setTemplateLocation( final URL value ) 2081 { 2082 this.templateLocation = value; 2083 } 2084 2085 /** 2086 * Gets the encoding to use for reading templates. 2087 * 2088 * @return The encoding to use for reading templates. 2089 * 2090 * @see #setTemplateEncoding(java.lang.String) 2091 */ 2092 public final String getTemplateEncoding() 2093 { 2094 if ( this.templateEncoding == null ) 2095 { 2096 this.templateEncoding = getMessage( "buildSourceEncoding" ); 2097 2098 if ( this.isLoggable( Level.CONFIG ) ) 2099 { 2100 this.log( Level.CONFIG, getMessage( "defaultTemplateEncoding", this.templateEncoding ), null ); 2101 } 2102 } 2103 2104 return this.templateEncoding; 2105 } 2106 2107 /** 2108 * Sets the encoding to use for reading templates. 2109 * 2110 * @param value The new encoding to use for reading templates or {@code null}. 2111 * 2112 * @see #getTemplateEncoding() 2113 */ 2114 public final void setTemplateEncoding( final String value ) 2115 { 2116 this.templateEncoding = value; 2117 this.velocityEngine = null; 2118 } 2119 2120 /** 2121 * Gets the encoding to use for reading files. 2122 * 2123 * @return The encoding to use for reading files. 2124 * 2125 * @see #setInputEncoding(java.lang.String) 2126 */ 2127 public final String getInputEncoding() 2128 { 2129 if ( this.inputEncoding == null ) 2130 { 2131 this.inputEncoding = new InputStreamReader( new ByteArrayInputStream( NO_BYTES ) ).getEncoding(); 2132 2133 if ( this.isLoggable( Level.CONFIG ) ) 2134 { 2135 this.log( Level.CONFIG, getMessage( "defaultInputEncoding", this.inputEncoding ), null ); 2136 } 2137 } 2138 2139 return this.inputEncoding; 2140 } 2141 2142 /** 2143 * Sets the encoding to use for reading files. 2144 * 2145 * @param value The new encoding to use for reading files or {@code null}. 2146 * 2147 * @see #getInputEncoding() 2148 */ 2149 public final void setInputEncoding( final String value ) 2150 { 2151 this.inputEncoding = value; 2152 } 2153 2154 /** 2155 * Gets the encoding to use for writing files. 2156 * 2157 * @return The encoding to use for writing files. 2158 * 2159 * @see #setOutputEncoding(java.lang.String) 2160 */ 2161 public final String getOutputEncoding() 2162 { 2163 if ( this.outputEncoding == null ) 2164 { 2165 this.outputEncoding = new OutputStreamWriter( new ByteArrayOutputStream() ).getEncoding(); 2166 2167 if ( this.isLoggable( Level.CONFIG ) ) 2168 { 2169 this.log( Level.CONFIG, getMessage( "defaultOutputEncoding", this.outputEncoding ), null ); 2170 } 2171 } 2172 2173 return this.outputEncoding; 2174 } 2175 2176 /** 2177 * Sets the encoding to use for writing files. 2178 * 2179 * @param value The encoding to use for writing files or {@code null}. 2180 * 2181 * @see #getOutputEncoding() 2182 */ 2183 public final void setOutputEncoding( final String value ) 2184 { 2185 this.outputEncoding = value; 2186 } 2187 2188 /** 2189 * Gets the default template profile. 2190 * <p>The default template profile is controlled by system property 2191 * {@code org.jomc.tools.JomcTool.defaultTemplateProfile} holding the name of the template profile to use by 2192 * default. If that property is not set, the {@code jomc-java} default is returned.</p> 2193 * 2194 * @return The default template profile. 2195 * 2196 * @see #setDefaultTemplateProfile(java.lang.String) 2197 * 2198 * @deprecated The {@code static} modifier of this method and support to setup the default template profile using 2199 * a system property will be removed in version 2.0. 2200 */ 2201 @Deprecated 2202 public static String getDefaultTemplateProfile() 2203 { 2204 if ( defaultTemplateProfile == null ) 2205 { 2206 defaultTemplateProfile = System.getProperty( "org.jomc.tools.JomcTool.defaultTemplateProfile", 2207 DEFAULT_TEMPLATE_PROFILE ); 2208 2209 } 2210 2211 return defaultTemplateProfile; 2212 } 2213 2214 /** 2215 * Sets the default template profile. 2216 * 2217 * @param value The new default template profile or {@code null}. 2218 * 2219 * @see #getDefaultTemplateProfile() 2220 * 2221 * @deprecated The {@code static} modifier of this method will be removed in version 2.0. 2222 */ 2223 @Deprecated 2224 public static void setDefaultTemplateProfile( final String value ) 2225 { 2226 defaultTemplateProfile = value; 2227 } 2228 2229 /** 2230 * Gets the template profile of the instance. 2231 * 2232 * @return The template profile of the instance. 2233 * 2234 * @see #getDefaultTemplateProfile() 2235 * @see #setTemplateProfile(java.lang.String) 2236 */ 2237 public final String getTemplateProfile() 2238 { 2239 if ( this.templateProfile == null ) 2240 { 2241 this.templateProfile = getDefaultTemplateProfile(); 2242 2243 if ( this.isLoggable( Level.CONFIG ) ) 2244 { 2245 this.log( Level.CONFIG, getMessage( "defaultTemplateProfile", this.templateProfile ), null ); 2246 } 2247 } 2248 2249 return this.templateProfile; 2250 } 2251 2252 /** 2253 * Sets the template profile of the instance. 2254 * 2255 * @param value The new template profile of the instance or {@code null}. 2256 * 2257 * @see #getTemplateProfile() 2258 */ 2259 public final void setTemplateProfile( final String value ) 2260 { 2261 this.templateProfile = value; 2262 } 2263 2264 /** 2265 * Gets the indentation string of the instance. 2266 * 2267 * @return The indentation string of the instance. 2268 * 2269 * @see #setIndentation(java.lang.String) 2270 */ 2271 public final String getIndentation() 2272 { 2273 if ( this.indentation == null ) 2274 { 2275 this.indentation = " "; 2276 2277 if ( this.isLoggable( Level.CONFIG ) ) 2278 { 2279 this.log( Level.CONFIG, getMessage( "defaultIndentation", 2280 StringEscapeUtils.escapeJava( this.indentation ) ), null ); 2281 2282 } 2283 } 2284 2285 return this.indentation; 2286 } 2287 2288 /** 2289 * Gets an indentation string for a given indentation level. 2290 * 2291 * @param level The indentation level to get an indentation string for. 2292 * 2293 * @return The indentation string for {@code level}. 2294 * 2295 * @throws IllegalArgumentException if {@code level} is negative. 2296 * 2297 * @see #getIndentation() 2298 */ 2299 public final String getIndentation( final int level ) 2300 { 2301 if ( level < 0 ) 2302 { 2303 throw new IllegalArgumentException( Integer.toString( level ) ); 2304 } 2305 2306 Map<String, String> map = this.indentationCache == null ? null : this.indentationCache.get(); 2307 2308 if ( map == null ) 2309 { 2310 map = new HashMap<String, String>(); 2311 this.indentationCache = new SoftReference<Map<String, String>>( map ); 2312 } 2313 2314 final String key = this.getIndentation() + "|" + level; 2315 String idt = map.get( key ); 2316 2317 if ( idt == null ) 2318 { 2319 final StringBuilder b = new StringBuilder( this.getIndentation().length() * level ); 2320 2321 for ( int i = level; i > 0; i-- ) 2322 { 2323 b.append( this.getIndentation() ); 2324 } 2325 2326 idt = b.toString(); 2327 map.put( key, idt ); 2328 } 2329 2330 return idt; 2331 } 2332 2333 /** 2334 * Sets the indentation string of the instance. 2335 * 2336 * @param value The new indentation string of the instance or {@code null}. 2337 * 2338 * @see #getIndentation() 2339 */ 2340 public final void setIndentation( final String value ) 2341 { 2342 this.indentation = value; 2343 } 2344 2345 /** 2346 * Gets the line separator of the instance. 2347 * 2348 * @return The line separator of the instance. 2349 * 2350 * @see #setLineSeparator(java.lang.String) 2351 */ 2352 public final String getLineSeparator() 2353 { 2354 if ( this.lineSeparator == null ) 2355 { 2356 this.lineSeparator = System.getProperty( "line.separator", "\n" ); 2357 2358 if ( this.isLoggable( Level.CONFIG ) ) 2359 { 2360 this.log( Level.CONFIG, getMessage( "defaultLineSeparator", 2361 StringEscapeUtils.escapeJava( this.lineSeparator ) ), null ); 2362 2363 } 2364 } 2365 2366 return this.lineSeparator; 2367 } 2368 2369 /** 2370 * Sets the line separator of the instance. 2371 * 2372 * @param value The new line separator of the instance or {@code null}. 2373 * 2374 * @see #getLineSeparator() 2375 */ 2376 public final void setLineSeparator( final String value ) 2377 { 2378 this.lineSeparator = value; 2379 } 2380 2381 /** 2382 * Gets the locale of the instance. 2383 * 2384 * @return The locale of the instance. 2385 * 2386 * @see #setLocale(java.util.Locale) 2387 * 2388 * @since 1.2 2389 */ 2390 public final Locale getLocale() 2391 { 2392 if ( this.locale == null ) 2393 { 2394 this.locale = Locale.ENGLISH; 2395 2396 if ( this.isLoggable( Level.CONFIG ) ) 2397 { 2398 this.log( Level.CONFIG, getMessage( "defaultLocale", this.locale ), null ); 2399 } 2400 } 2401 2402 return this.locale; 2403 } 2404 2405 /** 2406 * Sets the locale of the instance. 2407 * 2408 * @param value The new locale of the instance or {@code null}. 2409 * 2410 * @see #getLocale() 2411 * 2412 * @since 1.2 2413 */ 2414 public final void setLocale( final Locale value ) 2415 { 2416 this.locale = value; 2417 } 2418 2419 /** 2420 * Gets a velocity template for a given name. 2421 * <p>This method searches templates at the following locations in the shown order. 2422 * <ol> 2423 * <li><code>org/jomc/tools/templates/{@link #getTemplateProfile() profile}/{@link #getLocale() language}/<i>templateName</i></code></li> 2424 * <li><code>org/jomc/tools/templates/{@link #getTemplateProfile() profile}/<i>templateName</i></code></li> 2425 * <li><code>org/jomc/tools/templates/{@link #getDefaultTemplateProfile() default profile}/{@link #getLocale() language}/<i>templateName</i></code></li> 2426 * <li><code>org/jomc/tools/templates/{@link #getDefaultTemplateProfile() default profile}/<i>templateName</i></code></li> 2427 * </ol></p> 2428 * 2429 * @param templateName The name of the template to get. 2430 * 2431 * @return The template matching {@code templateName}. 2432 * 2433 * @throws NullPointerException if {@code templateName} is {@code null}. 2434 * @throws IOException if getting the template fails. 2435 * 2436 * @see #getLocale() 2437 * @see #getTemplateProfile() 2438 * @see #getTemplateEncoding() 2439 * @see #getVelocityEngine() 2440 */ 2441 public Template getVelocityTemplate( final String templateName ) throws IOException 2442 { 2443 if ( templateName == null ) 2444 { 2445 throw new NullPointerException( "templateName" ); 2446 } 2447 2448 String location = null; 2449 Template template = null; 2450 final String key = this.getLocale() + "|" + this.getTemplateProfile() + "|" + getDefaultTemplateProfile() 2451 + "|" + templateName; 2452 2453 Map<String, String> map = this.templateLocationsCache == null ? null : this.templateLocationsCache.get(); 2454 2455 if ( map == null ) 2456 { 2457 map = new HashMap<String, String>( 32 ); 2458 this.templateLocationsCache = new SoftReference<Map<String, String>>( map ); 2459 } 2460 2461 location = map.get( key ); 2462 2463 if ( location == null && !map.containsKey( key ) ) 2464 { 2465 if ( !StringUtils.EMPTY.equals( this.getLocale().getLanguage() ) ) 2466 { 2467 location = TEMPLATE_PREFIX + this.getTemplateProfile() + "/" + this.getLocale().getLanguage() + "/" 2468 + templateName; 2469 2470 template = this.findVelocityTemplate( location ); 2471 } 2472 2473 if ( template == null ) 2474 { 2475 location = TEMPLATE_PREFIX + this.getTemplateProfile() + "/" + templateName; 2476 template = this.findVelocityTemplate( location ); 2477 } 2478 2479 if ( template == null && !StringUtils.EMPTY.equals( this.getLocale().getLanguage() ) ) 2480 { 2481 location = TEMPLATE_PREFIX + getDefaultTemplateProfile() + "/" + this.getLocale().getLanguage() + "/" 2482 + templateName; 2483 2484 template = this.findVelocityTemplate( location ); 2485 } 2486 2487 if ( template == null ) 2488 { 2489 location = TEMPLATE_PREFIX + getDefaultTemplateProfile() + "/" + templateName; 2490 template = this.findVelocityTemplate( location ); 2491 } 2492 2493 map.put( key, location ); 2494 } 2495 else if ( location != null ) 2496 { 2497 template = this.findVelocityTemplate( location ); 2498 } 2499 2500 if ( template == null ) 2501 { 2502 throw new IOException( getMessage( "noSuchTemplate", templateName ) ); 2503 } 2504 2505 if ( this.isLoggable( Level.FINER ) ) 2506 { 2507 this.log( Level.FINER, getMessage( "templateInfo", templateName, location ), null ); 2508 } 2509 2510 return template; 2511 } 2512 2513 /** 2514 * Notifies registered listeners. 2515 * 2516 * @param level The level of the event. 2517 * @param message The message of the event or {@code null}. 2518 * @param throwable The throwable of the event or {@code null}. 2519 * 2520 * @throws NullPointerException if {@code level} is {@code null}. 2521 * 2522 * @see #getListeners() 2523 * @see #isLoggable(java.util.logging.Level) 2524 */ 2525 public void log( final Level level, final String message, final Throwable throwable ) 2526 { 2527 if ( level == null ) 2528 { 2529 throw new NullPointerException( "level" ); 2530 } 2531 2532 if ( this.isLoggable( level ) ) 2533 { 2534 for ( int i = this.getListeners().size() - 1; i >= 0; i-- ) 2535 { 2536 this.getListeners().get( i ).onLog( level, message, throwable ); 2537 } 2538 } 2539 } 2540 2541 private String getJavaPackageName( final String identifier ) 2542 { 2543 if ( identifier == null ) 2544 { 2545 throw new NullPointerException( "identifier" ); 2546 } 2547 2548 final int idx = identifier.lastIndexOf( '.' ); 2549 return idx != -1 ? identifier.substring( 0, idx ) : ""; 2550 } 2551 2552 private Template findVelocityTemplate( final String location ) throws IOException 2553 { 2554 try 2555 { 2556 return this.getVelocityEngine().getTemplate( location, this.getTemplateEncoding() ); 2557 } 2558 catch ( final ResourceNotFoundException e ) 2559 { 2560 if ( this.isLoggable( Level.FINER ) ) 2561 { 2562 this.log( Level.FINER, getMessage( "templateNotFound", location ), null ); 2563 } 2564 2565 return null; 2566 } 2567 catch ( final ParseErrorException e ) 2568 { 2569 String m = getMessage( e ); 2570 m = m == null ? "" : " " + m; 2571 2572 // JDK: As of JDK 6, "new IOException( message, cause )". 2573 throw (IOException) new IOException( getMessage( "invalidTemplate", location, m ) ).initCause( e ); 2574 } 2575 catch ( final VelocityException e ) 2576 { 2577 String m = getMessage( e ); 2578 m = m == null ? "" : " " + m; 2579 2580 // JDK: As of JDK 6, "new IOException( message, cause )". 2581 throw (IOException) new IOException( getMessage( "velocityException", location, m ) ).initCause( e ); 2582 } 2583 } 2584 2585 private java.util.Properties getTemplateProfileProperties( final String profileName, final String language ) 2586 { 2587 Map<String, java.util.Properties> map = 2588 this.templateProfilePropertiesCache == null ? null : this.templateProfilePropertiesCache.get(); 2589 2590 if ( map == null ) 2591 { 2592 map = new HashMap<String, java.util.Properties>(); 2593 this.templateProfilePropertiesCache = new SoftReference<Map<String, java.util.Properties>>( map ); 2594 } 2595 2596 final String key = profileName + "|" + language; 2597 java.util.Properties profileProperties = map.get( key ); 2598 2599 if ( profileProperties == null ) 2600 { 2601 InputStream in = null; 2602 profileProperties = new java.util.Properties(); 2603 final String resourceName = "/" + TEMPLATE_PREFIX + profileName + ( language == null ? "" : "/" + language ) 2604 + "/context.properties"; 2605 2606 try 2607 { 2608 in = this.getClass().getResourceAsStream( resourceName ); 2609 2610 if ( in != null ) 2611 { 2612 if ( this.isLoggable( Level.CONFIG ) ) 2613 { 2614 this.log( Level.CONFIG, getMessage( "contextPropertiesFound", resourceName ), null ); 2615 } 2616 2617 profileProperties.load( in ); 2618 } 2619 else if ( this.isLoggable( Level.CONFIG ) ) 2620 { 2621 this.log( Level.CONFIG, getMessage( "contextPropertiesNotFound", resourceName ), null ); 2622 } 2623 2624 map.put( key, profileProperties ); 2625 } 2626 catch ( final IOException e ) 2627 { 2628 this.log( Level.SEVERE, getMessage( e ), e ); 2629 } 2630 finally 2631 { 2632 try 2633 { 2634 if ( in != null ) 2635 { 2636 in.close(); 2637 } 2638 } 2639 catch ( final IOException e ) 2640 { 2641 this.log( Level.SEVERE, getMessage( e ), e ); 2642 } 2643 } 2644 } 2645 2646 return profileProperties; 2647 } 2648 2649 private void mergeTemplateProfileProperties( final String profileName, final String language, 2650 final VelocityContext velocityContext ) 2651 { 2652 final java.util.Properties templateProfileProperties = 2653 this.getTemplateProfileProperties( profileName, language ); 2654 2655 for ( final Enumeration<?> e = templateProfileProperties.propertyNames(); e.hasMoreElements(); ) 2656 { 2657 final String name = e.nextElement().toString(); 2658 final String value = templateProfileProperties.getProperty( name ); 2659 final String[] values = value.split( "\\|" ); 2660 2661 if ( !velocityContext.containsKey( name ) ) 2662 { 2663 try 2664 { 2665 if ( values.length > 1 ) 2666 { 2667 final Class<?> valueClass = Class.forName( values[0] ); 2668 velocityContext.put( name, valueClass.getConstructor( String.class ).newInstance( values[1] ) ); 2669 } 2670 else if ( value.contains( "|" ) ) 2671 { 2672 velocityContext.put( name, Class.forName( values[0] ).newInstance() ); 2673 } 2674 else 2675 { 2676 velocityContext.put( name, value ); 2677 } 2678 } 2679 catch ( final InstantiationException ex ) 2680 { 2681 this.log( Level.SEVERE, getMessage( ex ), ex ); 2682 } 2683 catch ( final IllegalAccessException ex ) 2684 { 2685 this.log( Level.SEVERE, getMessage( ex ), ex ); 2686 } 2687 catch ( final InvocationTargetException ex ) 2688 { 2689 this.log( Level.SEVERE, getMessage( ex ), ex ); 2690 } 2691 catch ( final NoSuchMethodException ex ) 2692 { 2693 this.log( Level.SEVERE, getMessage( ex ), ex ); 2694 } 2695 catch ( final ClassNotFoundException ex ) 2696 { 2697 this.log( Level.SEVERE, getMessage( ex ), ex ); 2698 } 2699 } 2700 } 2701 } 2702 2703 private Set<String> getJavaKeywords() 2704 { 2705 Reader in = null; 2706 Set<String> set = this.javaKeywordsCache == null ? null : this.javaKeywordsCache.get(); 2707 2708 try 2709 { 2710 if ( set == null ) 2711 { 2712 set = new HashSet<String>( 64 ); 2713 this.javaKeywordsCache = new SoftReference<Set<String>>( set ); 2714 } 2715 2716 in = new InputStreamReader( this.getClass().getResourceAsStream( 2717 "/" + this.getClass().getPackage().getName().replace( ".", "/" ) + "/JavaKeywords.txt" ), "UTF-8" ); 2718 2719 set.addAll( IOUtils.readLines( in ) ); 2720 } 2721 catch ( final IOException e ) 2722 { 2723 throw new IllegalStateException( getMessage( e ), e ); 2724 } 2725 finally 2726 { 2727 try 2728 { 2729 if ( in != null ) 2730 { 2731 in.close(); 2732 } 2733 } 2734 catch ( final IOException e ) 2735 { 2736 throw new IllegalStateException( getMessage( e ), e ); 2737 } 2738 } 2739 2740 return set; 2741 } 2742 2743 private static String getMessage( final String key, final Object... arguments ) 2744 { 2745 return MessageFormat.format( ResourceBundle.getBundle( 2746 JomcTool.class.getName().replace( '.', '/' ) ).getString( key ), arguments ); 2747 2748 } 2749 2750 private static String getMessage( final Throwable t ) 2751 { 2752 return t != null ? t.getMessage() != null ? t.getMessage() : getMessage( t.getCause() ) : null; 2753 } 2754 2755 }