1 /* 2 * Copyright (c) 2003 The Visigoth Software Society. All rights 3 * reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in 14 * the documentation and/or other materials provided with the 15 * distribution. 16 * 17 * 3. The end-user documentation included with the redistribution, if 18 * any, must include the following acknowledgement: 19 * "This product includes software developed by the 20 * Visigoth Software Society (http://www.visigoths.org/)." 21 * Alternately, this acknowledgement may appear in the software itself, 22 * if and wherever such third-party acknowledgements normally appear. 23 * 24 * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the 25 * project contributors may be used to endorse or promote products derived 26 * from this software without prior written permission. For written 27 * permission, please contact visigoths@visigoths.org. 28 * 29 * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth" 30 * nor may "FreeMarker" or "Visigoth" appear in their names 31 * without prior written permission of the Visigoth Software Society. 32 * 33 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 34 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 35 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 36 * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR 37 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 38 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 39 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 40 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 41 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 42 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 43 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 44 * SUCH DAMAGE. 45 * ==================================================================== 46 * 47 * This software consists of voluntary contributions made by many 48 * individuals on behalf of the Visigoth Software Society. For more 49 * information on the Visigoth Software Society, please see 50 * http://www.visigoths.org/ 51 */ 52 53 package freemarker.ext.beans; 54 55 import java.beans.BeanInfo; 56 import java.beans.IndexedPropertyDescriptor; 57 import java.beans.IntrospectionException; 58 import java.beans.Introspector; 59 import java.beans.MethodDescriptor; 60 import java.beans.PropertyDescriptor; 61 import java.io.InputStream; 62 import java.lang.reflect.AccessibleObject; 63 import java.lang.reflect.Array; 64 import java.lang.reflect.Constructor; 65 import java.lang.reflect.Field; 66 import java.lang.reflect.InvocationTargetException; 67 import java.lang.reflect.Method; 68 import java.lang.reflect.Modifier; 69 import java.math.BigDecimal; 70 import java.math.BigInteger; 71 import java.util.Arrays; 72 import java.util.Collection; 73 import java.util.Collections; 74 import java.util.Date; 75 import java.util.Enumeration; 76 import java.util.HashMap; 77 import java.util.HashSet; 78 import java.util.Iterator; 79 import java.util.List; 80 import java.util.Map; 81 import java.util.Properties; 82 import java.util.ResourceBundle; 83 import java.util.Set; 84 import java.util.StringTokenizer; 85 86 import freemarker.ext.util.IdentityHashMap; 87 import freemarker.ext.util.ModelCache; 88 import freemarker.ext.util.ModelFactory; 89 import freemarker.ext.util.WrapperTemplateModel; 90 import freemarker.log.Logger; 91 import freemarker.template.AdapterTemplateModel; 92 import freemarker.template.ObjectWrapper; 93 import freemarker.template.TemplateBooleanModel; 94 import freemarker.template.TemplateCollectionModel; 95 import freemarker.template.TemplateDateModel; 96 import freemarker.template.TemplateHashModel; 97 import freemarker.template.TemplateModel; 98 import freemarker.template.TemplateModelException; 99 import freemarker.template.TemplateNumberModel; 100 import freemarker.template.TemplateScalarModel; 101 import freemarker.template.TemplateSequenceModel; 102 import freemarker.template.utility.ClassUtil; 103 import freemarker.template.utility.Collections12; 104 import freemarker.template.utility.SecurityUtilities; 105 import freemarker.template.utility.UndeclaredThrowableException; 106 107 /** 108 * Utility class that provides generic services to reflection classes. 109 * It handles all polymorphism issues in the {@link #wrap(Object)} and {@link #unwrap(TemplateModel)} methods. 110 * @author Attila Szegedi 111 * @version $Id: BeansWrapper.java,v 1.91.2.13 2007/04/02 13:08:59 szegedia Exp $ 112 */ 113 public class BeansWrapper implements ObjectWrapper 114 { 115 public static final Object CAN_NOT_UNWRAP = new Object(); 116 private static final Class BIGINTEGER_CLASS = java.math.BigInteger.class; 117 private static final Class BOOLEAN_CLASS = Boolean.class; 118 private static final Class CHARACTER_CLASS = Character.class; 119 private static final Class COLLECTION_CLASS = Collection.class; 120 private static final Class DATE_CLASS = Date.class; 121 private static final Class HASHADAPTER_CLASS = HashAdapter.class; 122 private static final Class ITERABLE_CLASS; 123 private static final Class LIST_CLASS = List.class; 124 private static final Class MAP_CLASS = Map.class; 125 private static final Class NUMBER_CLASS = Number.class; 126 private static final Class OBJECT_CLASS = Object.class; 127 private static final Class SEQUENCEADAPTER_CLASS = SequenceAdapter.class; 128 private static final Class SET_CLASS = Set.class; 129 private static final Class SETADAPTER_CLASS = SetAdapter.class; 130 private static final Class STRING_CLASS = String.class; 131 static { 132 Class iterable; 133 try { 134 iterable = Class.forName("java.lang.Iterable"); 135 } 136 catch(ClassNotFoundException e) { 137 // We're running on a pre-1.5 JRE 138 iterable = null; 139 } 140 ITERABLE_CLASS = iterable; 141 } 142 143 // When this property is true, some things are stricter. This is mostly to 144 // catch anomalous things in development that can otherwise be valid situations 145 // for our users. 146 private static final boolean DEVELOPMENT = "true".equals(SecurityUtilities.getSystemProperty("freemarker.development")); 147 148 private static final Constructor ENUMS_MODEL_CTOR = enumsModelCtor(); 149 150 private static final Logger logger = Logger.getLogger("freemarker.beans"); 151 152 private static final Set UNSAFE_METHODS = createUnsafeMethodsSet(); 153 154 static final Object GENERIC_GET_KEY = new Object(); 155 private static final Object CONSTRUCTORS = new Object(); 156 private static final Object ARGTYPES = new Object(); 157 158 /** 159 * The default instance of BeansWrapper 160 */ 161 private static final BeansWrapper INSTANCE = new BeansWrapper(); 162 163 // Cache of hash maps that contain already discovered properties and methods 164 // for a specified class. Each key is a Class, each value is a hash map. In 165 // that hash map, each key is a property/method name, each value is a 166 // MethodDescriptor or a PropertyDescriptor assigned to that property/method. 167 private final Map classCache = new HashMap(); 168 private Set cachedClassNames = new HashSet(); 169 170 private final StaticModels staticModels = new StaticModels(this); 171 private final ClassBasedModelFactory enumModels = createEnumModels(this); 172 173 private final ModelCache modelCache = new BeansModelCache(this); 174 175 private final BooleanModel FALSE = new BooleanModel(Boolean.FALSE, this); 176 private final BooleanModel TRUE = new BooleanModel(Boolean.TRUE, this); 177 178 /** 179 * At this level of exposure, all methods and properties of the 180 * wrapped objects are exposed to the template. 181 */ 182 public static final int EXPOSE_ALL = 0; 183 184 /** 185 * At this level of exposure, all methods and properties of the wrapped 186 * objects are exposed to the template except methods that are deemed 187 * not safe. The not safe methods are java.lang.Object methods wait() and 188 * notify(), java.lang.Class methods getClassLoader() and newInstance(), 189 * java.lang.reflect.Method and java.lang.reflect.Constructor invoke() and 190 * newInstance() methods, all java.lang.reflect.Field set methods, all 191 * java.lang.Thread and java.lang.ThreadGroup methods that can change its 192 * state, as well as the usual suspects in java.lang.System and 193 * java.lang.Runtime. 194 */ 195 public static final int EXPOSE_SAFE = 1; 196 197 /** 198 * At this level of exposure, only property getters are exposed. 199 * Additionally, property getters that map to unsafe methods are not 200 * exposed (i.e. Class.classLoader and Thread.contextClassLoader). 201 */ 202 public static final int EXPOSE_PROPERTIES_ONLY = 2; 203 204 /** 205 * At this level of exposure, no bean properties and methods are exposed. 206 * Only map items, resource bundle items, and objects retrieved through 207 * the generic get method (on objects of classes that have a generic get 208 * method) can be retrieved through the hash interface. You might want to 209 * call {@link #setMethodsShadowItems(boolean)} with <tt>false</tt> value to 210 * speed up map item retrieval. 211 */ 212 public static final int EXPOSE_NOTHING = 3; 213 214 private int exposureLevel = EXPOSE_SAFE; 215 private TemplateModel nullModel = null; 216 private boolean methodsShadowItems = true; 217 private boolean exposeFields = false; 218 private int defaultDateType = TemplateDateModel.UNKNOWN; 219 220 private ObjectWrapper outerIdentity = this; 221 private boolean simpleMapWrapper; 222 private boolean strict = false; 223 224 /** 225 * Creates a new instance of BeansWrapper. The newly created instance 226 * will use the null reference as its null object, it will use 227 * {@link #EXPOSE_SAFE} method exposure level, and will not cache 228 * model instances. 229 */ 230 public BeansWrapper() 231 { 232 } 233 234 /** 235 * @see #setStrict(boolean) 236 */ 237 public boolean isStrict() { 238 return strict; 239 } 240 241 /** 242 * Specifies if an attempt to read a bean property that doesn't exist in the 243 * wrapped object should throw an {@link InvalidPropertyException}. 244 * 245 * <p>If this property is <tt>false</tt> (the default) then an attempt to read 246 * a missing bean property is the same as reading an existing bean property whose 247 * value is <tt>null</tt>. The template can't tell the difference, and thus always 248 * can use <tt>?default('something')</tt> and <tt>?exists</tt> and similar built-ins 249 * to handle the situation. 250 * 251 * <p>If this property is <tt>true</tt> then an attempt to read a bean propertly in 252 * the template (like <tt>myBean.aProperty</tt>) that doesn't exist in the bean 253 * object (as opposed to just holding <tt>null</tt> value) will cause 254 * {@link InvalidPropertyException}, which can't be suppressed in the template 255 * (not even with <tt>myBean.noSuchProperty?default('something')</tt>). This way 256 * <tt>?default('something')</tt> and <tt>?exists</tt> and similar built-ins can be used to 257 * handle existing properties whose value is <tt>null</tt>, without the risk of 258 * hiding typos in the property names. Typos will always cause error. But mind you, it 259 * goes against the basic approach of FreeMarker, so use this feature only if you really 260 * know what are you doing. 261 */ 262 public void setStrict(boolean strict) { 263 this.strict = strict; 264 } 265 266 /** 267 * When wrapping an object, the BeansWrapper commonly needs to wrap 268 * "sub-objects", for example each element in a wrapped collection. 269 * Normally it wraps these objects using itself. However, this makes 270 * it difficult to delegate to a BeansWrapper as part of a custom 271 * aggregate ObjectWrapper. This method lets you set the ObjectWrapper 272 * which will be used to wrap the sub-objects. 273 * @param outerIdentity the aggregate ObjectWrapper 274 */ 275 public void setOuterIdentity(ObjectWrapper outerIdentity) 276 { 277 this.outerIdentity = outerIdentity; 278 } 279 280 /** 281 * By default returns <tt>this</tt>. 282 * @see #setOuterIdentity(ObjectWrapper) 283 */ 284 public ObjectWrapper getOuterIdentity() 285 { 286 return outerIdentity; 287 } 288 289 /** 290 * By default the BeansWrapper wraps classes implementing 291 * java.util.Map using {@link MapModel}. Setting this flag will 292 * cause it to use a {@link SimpleMapModel} instead. The biggest 293 * difference is that when using a {@link SimpleMapModel}, the 294 * map will be visible as <code>TemplateHashModelEx</code>, 295 * and the subvariables will be the content of the map, 296 * without the other methods and properties of the map object. 297 * @param simpleMapWrapper enable simple map wrapping 298 */ 299 public void setSimpleMapWrapper(boolean simpleMapWrapper) 300 { 301 this.simpleMapWrapper = simpleMapWrapper; 302 } 303 304 /** 305 * Tells whether Maps are exposed as simple maps, without access to their 306 * method. See {@link #setSimpleMapWrapper(boolean)} for details. 307 * @return true if Maps are exposed as simple hashes, false if they're 308 * exposed as full JavaBeans. 309 */ 310 public boolean isSimpleMapWrapper() 311 { 312 return simpleMapWrapper; 313 } 314 315 /** 316 * Sets the method exposure level. By default, set to <code>EXPOSE_SAFE</code>. 317 * @param exposureLevel can be any of the <code>EXPOSE_xxx</code> 318 * constants. 319 */ 320 public void setExposureLevel(int exposureLevel) 321 { 322 if(exposureLevel < EXPOSE_ALL || exposureLevel > EXPOSE_NOTHING) 323 { 324 throw new IllegalArgumentException("Illegal exposure level " + exposureLevel); 325 } 326 this.exposureLevel = exposureLevel; 327 } 328 329 int getExposureLevel() 330 { 331 return exposureLevel; 332 } 333 334 /** 335 * Controls whether public instance fields of classes are exposed to 336 * templates. 337 * @param exposeFields if set to true, public instance fields of classes 338 * that do not have a property getter defined can be accessed directly by 339 * their name. If there is a property getter for a property of the same 340 * name as the field (i.e. getter "getFoo()" and field "foo"), then 341 * referring to "foo" in template invokes the getter. If set to false, no 342 * access to public instance fields of classes is given. Default is false. 343 */ 344 public void setExposeFields(boolean exposeFields) 345 { 346 this.exposeFields = exposeFields; 347 } 348 349 /** 350 * Returns whether exposure of public instance fields of classes is 351 * enabled. See {@link #setExposeFields(boolean)} for details. 352 * @return true if public instance fields are exposed, false otherwise. 353 */ 354 public boolean isExposeFields() 355 { 356 return exposeFields; 357 } 358 359 /** 360 * Sets whether methods shadow items in beans. When true (this is the 361 * default value), <code>${object.name}</code> will first try to locate 362 * a bean method or property with the specified name on the object, and 363 * only if it doesn't find it will it try to call 364 * <code>object.get(name)</code>, the so-called "generic get method" that 365 * is usually used to access items of a container (i.e. elements of a map). 366 * When set to false, the lookup order is reversed and generic get method 367 * is called first, and only if it returns null is method lookup attempted. 368 */ 369 public synchronized void setMethodsShadowItems(boolean methodsShadowItems) 370 { 371 this.methodsShadowItems = methodsShadowItems; 372 } 373 374 boolean isMethodsShadowItems() 375 { 376 return methodsShadowItems; 377 } 378 379 /** 380 * Sets the default date type to use for date models that result from 381 * a plain <tt>java.util.Date</tt> instead of <tt>java.sql.Date</tt> or 382 * <tt>java.sql.Time</tt> or <tt>java.sql.Timestamp</tt>. Default value is 383 * {@link TemplateDateModel#UNKNOWN}. 384 * @param defaultDateType the new default date type. 385 */ 386 public synchronized void setDefaultDateType(int defaultDateType) { 387 this.defaultDateType = defaultDateType; 388 } 389 390 /** 391 * Returns the default date type. See {@link #setDefaultDateType(int)} for 392 * details. 393 * @return the default date type 394 */ 395 protected int getDefaultDateType() { 396 return defaultDateType; 397 } 398 399 /** 400 * Sets whether this wrapper caches model instances. Default is false. 401 * When set to true, calling {@link #wrap(Object)} multiple times for 402 * the same object will likely return the same model (although there is 403 * no guarantee as the cache items can be cleared anytime). 404 */ 405 public void setUseCache(boolean useCache) 406 { 407 modelCache.setUseCache(useCache); 408 } 409 410 /** 411 * Sets the null model. This model is returned from the 412 * {@link #wrap(Object)} method whenever the underlying object 413 * reference is null. It defaults to null reference, which is dealt 414 * with quite strictly on engine level, however you can substitute an 415 * arbitrary (perhaps more lenient) model, such as 416 * {@link freemarker.template.TemplateScalarModel#EMPTY_STRING}. 417 */ 418 public void setNullModel(TemplateModel nullModel) 419 { 420 this.nullModel = nullModel; 421 } 422 423 /** 424 * Returns the default instance of the wrapper. This instance is used 425 * when you construct various bean models without explicitly specifying 426 * a wrapper. It is also returned by 427 * {@link freemarker.template.ObjectWrapper#BEANS_WRAPPER} 428 * and this is the sole instance that is used by the JSP adapter. 429 * You can modify the properties of the default instance (caching, 430 * exposure level, null model) to affect its operation. By default, the 431 * default instance is not caching, uses the <code>EXPOSE_SAFE</code> 432 * exposure level, and uses null reference as the null model. 433 */ 434 public static final BeansWrapper getDefaultInstance() 435 { 436 return INSTANCE; 437 } 438 439 /** 440 * Wraps the object with a template model that is most specific for the object's 441 * class. Specifically: 442 * <ul> 443 * <li>if the object is null, returns the {@link #setNullModel(TemplateModel) null model},</li> 444 * <li>if the object is a Number returns a {@link NumberModel} for it,</li> 445 * <li>if the object is a Date returns a {@link DateModel} for it,</li> 446 * <li>if the object is a Boolean returns 447 * {@link freemarker.template.TemplateBooleanModel#TRUE} or 448 * {@link freemarker.template.TemplateBooleanModel#FALSE}</li> 449 * <li>if the object is already a TemplateModel, returns it unchanged,</li> 450 * <li>if the object is an array, returns a {@link ArrayModel} for it 451 * <li>if the object is a Map, returns a {@link MapModel} for it 452 * <li>if the object is a Collection, returns a {@link CollectionModel} for it 453 * <li>if the object is an Iterator, returns a {@link IteratorModel} for it 454 * <li>if the object is an Enumeration, returns a {@link EnumerationModel} for it 455 * <li>if the object is a String, returns a {@link StringModel} for it 456 * <li>otherwise, returns a generic {@link BeanModel} for it. 457 * </ul> 458 */ 459 public TemplateModel wrap(Object object) throws TemplateModelException 460 { 461 if(object == null) 462 return nullModel; 463 return modelCache.getInstance(object); 464 } 465 466 /** 467 * @deprecated override {@link #getModelFactory(Class)} instead. Using this 468 * method will now bypass wrapper caching (if it is enabled) and always 469 * result in creation of a new wrapper. This method will be removed in 2.4 470 * @param object 471 * @param factory 472 */ 473 protected TemplateModel getInstance(Object object, ModelFactory factory) 474 { 475 return factory.create(object, this); 476 } 477 478 private final ModelFactory BOOLEAN_FACTORY = new ModelFactory() { 479 public TemplateModel create(Object object, ObjectWrapper wrapper) { 480 return ((Boolean)object).booleanValue() ? TRUE : FALSE; 481 } 482 }; 483 484 private static final ModelFactory ITERATOR_FACTORY = new ModelFactory() { 485 public TemplateModel create(Object object, ObjectWrapper wrapper) { 486 return new IteratorModel((Iterator)object, (BeansWrapper)wrapper); 487 } 488 }; 489 490 private static final ModelFactory ENUMERATION_FACTORY = new ModelFactory() { 491 public TemplateModel create(Object object, ObjectWrapper wrapper) { 492 return new EnumerationModel((Enumeration)object, (BeansWrapper)wrapper); 493 } 494 }; 495 496 protected ModelFactory getModelFactory(Class clazz) { 497 if(Map.class.isAssignableFrom(clazz)) { 498 return simpleMapWrapper ? SimpleMapModel.FACTORY : MapModel.FACTORY; 499 } 500 if(Collection.class.isAssignableFrom(clazz)) { 501 return CollectionModel.FACTORY; 502 } 503 if(Number.class.isAssignableFrom(clazz)) { 504 return NumberModel.FACTORY; 505 } 506 if(Date.class.isAssignableFrom(clazz)) { 507 return DateModel.FACTORY; 508 } 509 if(Boolean.class == clazz) { // Boolean is final 510 return BOOLEAN_FACTORY; 511 } 512 if(ResourceBundle.class.isAssignableFrom(clazz)) { 513 return ResourceBundleModel.FACTORY; 514 } 515 if(Iterator.class.isAssignableFrom(clazz)) { 516 return ITERATOR_FACTORY; 517 } 518 if(Enumeration.class.isAssignableFrom(clazz)) { 519 return ENUMERATION_FACTORY; 520 } 521 if(clazz.isArray()) { 522 return ArrayModel.FACTORY; 523 } 524 return StringModel.FACTORY; 525 } 526 527 /** 528 * Attempts to unwrap a model into underlying object. Generally, this 529 * method is the inverse of the {@link #wrap(Object)} method. In addition 530 * it will unwrap arbitrary {@link TemplateNumberModel} instances into 531 * a number, arbitrary {@link TemplateDateModel} instances into a date, 532 * {@link TemplateScalarModel} instances into a String, and 533 * {@link TemplateBooleanModel} instances into a Boolean. 534 * All other objects are returned unchanged. 535 */ 536 public Object unwrap(TemplateModel model) throws TemplateModelException 537 { 538 return unwrap(model, OBJECT_CLASS); 539 } 540 541 public Object unwrap(TemplateModel model, Class hint) 542 throws TemplateModelException 543 { 544 return unwrap(model, hint, null); 545 } 546 547 private Object unwrap(TemplateModel model, Class hint, Map recursionStops) 548 throws TemplateModelException 549 { 550 if(model == nullModel) { 551 return null; 552 } 553 554 boolean isBoolean = Boolean.TYPE == hint; 555 boolean isChar = Character.TYPE == hint; 556 557 // This is for transparent interop with other wrappers (and ourselves) 558 // Passing the hint allows i.e. a Jython-aware method that declares a 559 // PyObject as its argument to receive a PyObject from a JythonModel 560 // passed as an argument to TemplateMethodModelEx etc. 561 if(model instanceof AdapterTemplateModel) { 562 Object adapted = ((AdapterTemplateModel)model).getAdaptedObject( 563 hint); 564 if(hint.isInstance(adapted)) { 565 return adapted; 566 } 567 // Attempt numeric conversion 568 if(adapted instanceof Number && ((hint.isPrimitive() && !isChar && 569 !isBoolean) || NUMBER_CLASS.isAssignableFrom(hint))) { 570 Number number = convertUnwrappedNumber(hint, 571 (Number)adapted); 572 if(number != null) { 573 return number; 574 } 575 } 576 } 577 578 if(model instanceof WrapperTemplateModel) { 579 Object wrapped = ((WrapperTemplateModel)model).getWrappedObject(); 580 if(hint.isInstance(wrapped)) { 581 return wrapped; 582 } 583 // Attempt numeric conversion 584 if(wrapped instanceof Number && ((hint.isPrimitive() && !isChar && 585 !isBoolean) || NUMBER_CLASS.isAssignableFrom(hint))) { 586 Number number = convertUnwrappedNumber(hint, 587 (Number)wrapped); 588 if(number != null) { 589 return number; 590 } 591 } 592 } 593 594 // Translation of generic template models to POJOs. First give priority 595 // to various model interfaces based on the hint class. This helps us 596 // select the appropriate interface in multi-interface models when we 597 // know what is expected as the return type. 598 599 if(STRING_CLASS == hint) { 600 if(model instanceof TemplateScalarModel) { 601 return ((TemplateScalarModel)model).getAsString(); 602 } 603 // String is final, so no other conversion will work 604 return CAN_NOT_UNWRAP; 605 } 606 607 // Primitive numeric types & Number.class and its subclasses 608 if((hint.isPrimitive() && !isChar && !isBoolean) 609 || NUMBER_CLASS.isAssignableFrom(hint)) { 610 if(model instanceof TemplateNumberModel) { 611 Number number = convertUnwrappedNumber(hint, 612 ((TemplateNumberModel)model).getAsNumber()); 613 if(number != null) { 614 return number; 615 } 616 } 617 } 618 619 if(isBoolean || BOOLEAN_CLASS == hint) { 620 if(model instanceof TemplateBooleanModel) { 621 return ((TemplateBooleanModel)model).getAsBoolean() 622 ? Boolean.TRUE : Boolean.FALSE; 623 } 624 // Boolean is final, no other conversion will work 625 return CAN_NOT_UNWRAP; 626 } 627 628 if(MAP_CLASS == hint) { 629 if(model instanceof TemplateHashModel) { 630 return new HashAdapter((TemplateHashModel)model, this); 631 } 632 } 633 634 if(LIST_CLASS == hint) { 635 if(model instanceof TemplateSequenceModel) { 636 return new SequenceAdapter((TemplateSequenceModel)model, this); 637 } 638 } 639 640 if(SET_CLASS == hint) { 641 if(model instanceof TemplateCollectionModel) { 642 return new SetAdapter((TemplateCollectionModel)model, this); 643 } 644 } 645 646 if(COLLECTION_CLASS == hint 647 || ITERABLE_CLASS == hint) { 648 if(model instanceof TemplateCollectionModel) { 649 return new CollectionAdapter((TemplateCollectionModel)model, 650 this); 651 } 652 if(model instanceof TemplateSequenceModel) { 653 return new SequenceAdapter((TemplateSequenceModel)model, this); 654 } 655 } 656 657 // TemplateSequenceModels can be converted to arrays 658 if(hint.isArray()) { 659 if(model instanceof TemplateSequenceModel) { 660 if(recursionStops != null) { 661 Object retval = recursionStops.get(model); 662 if(retval != null) { 663 return retval; 664 } 665 } else { 666 recursionStops = 667 new IdentityHashMap(); 668 } 669 TemplateSequenceModel seq = (TemplateSequenceModel)model; 670 Class componentType = hint.getComponentType(); 671 Object array = Array.newInstance(componentType, seq.size()); 672 recursionStops.put(model, array); 673 try { 674 int size = seq.size(); 675 for (int i = 0; i < size; i++) { 676 Object val = unwrap(model, componentType, 677 recursionStops); 678 if(val == CAN_NOT_UNWRAP) { 679 return CAN_NOT_UNWRAP; 680 } 681 Array.set(array, i, val); 682 } 683 } finally { 684 recursionStops.remove(model); 685 } 686 return array; 687 } 688 // array classes are final, no other conversion will work 689 return CAN_NOT_UNWRAP; 690 } 691 692 // Allow one-char strings to be coerced to characters 693 if(isChar || hint == CHARACTER_CLASS) { 694 if(model instanceof TemplateScalarModel) { 695 String s = ((TemplateScalarModel)model).getAsString(); 696 if(s.length() == 1) { 697 return new Character(s.charAt(0)); 698 } 699 } 700 // Character is final, no other conversion will work 701 return CAN_NOT_UNWRAP; 702 } 703 704 if(DATE_CLASS.isAssignableFrom(hint)) { 705 if(model instanceof TemplateDateModel) { 706 Date date = ((TemplateDateModel)model).getAsDate(); 707 if(hint.isInstance(date)) { 708 return date; 709 } 710 } 711 } 712 713 // Translation of generic template models to POJOs. Since hint was of 714 // no help initially, now use an admittedly arbitrary order of 715 // interfaces. Note we still test for isInstance and isAssignableFrom 716 // to guarantee we return a compatible value. 717 if(model instanceof TemplateNumberModel) { 718 Number number = ((TemplateNumberModel)model).getAsNumber(); 719 if(hint.isInstance(number)) { 720 return number; 721 } 722 } 723 if(model instanceof TemplateDateModel) { 724 Date date = ((TemplateDateModel)model).getAsDate(); 725 if(hint.isInstance(date)) { 726 return date; 727 } 728 } 729 if(model instanceof TemplateScalarModel && 730 hint.isAssignableFrom(STRING_CLASS)) { 731 return ((TemplateScalarModel)model).getAsString(); 732 } 733 if(model instanceof TemplateBooleanModel && 734 hint.isAssignableFrom(BOOLEAN_CLASS)) { 735 return ((TemplateBooleanModel)model).getAsBoolean() 736 ? Boolean.TRUE : Boolean.FALSE; 737 } 738 if(model instanceof TemplateHashModel && hint.isAssignableFrom( 739 HASHADAPTER_CLASS)) { 740 return new HashAdapter((TemplateHashModel)model, this); 741 } 742 if(model instanceof TemplateSequenceModel 743 && hint.isAssignableFrom(SEQUENCEADAPTER_CLASS)) { 744 return new SequenceAdapter((TemplateSequenceModel)model, this); 745 } 746 if(model instanceof TemplateCollectionModel && 747 hint.isAssignableFrom(SETADAPTER_CLASS)) { 748 return new SetAdapter((TemplateCollectionModel)model, this); 749 } 750 751 // Last ditch effort - is maybe the model itself instance of the 752 // required type? 753 if(hint.isInstance(model)) { 754 return model; 755 } 756 757 return CAN_NOT_UNWRAP; 758 } 759 760 private static Number convertUnwrappedNumber(Class hint, Number number) 761 { 762 if(hint == Integer.TYPE || hint == Integer.class) { 763 return number instanceof Integer ? (Integer)number : 764 new Integer(number.intValue()); 765 } 766 if(hint == Long.TYPE || hint == Long.class) { 767 return number instanceof Long ? (Long)number : 768 new Long(number.longValue()); 769 } 770 if(hint == Float.TYPE || hint == Float.class) { 771 return number instanceof Float ? (Float)number : 772 new Float(number.longValue()); 773 } 774 if(hint == Double.TYPE 775 || hint == Double.class) { 776 return number instanceof Double ? (Double)number : 777 new Double(number.longValue()); 778 } 779 if(hint == Byte.TYPE || hint == Byte.class) { 780 return number instanceof Byte ? (Byte)number : 781 new Byte(number.byteValue()); 782 } 783 if(hint == Short.TYPE || hint == Short.class) { 784 return number instanceof Short ? (Short)number : 785 new Short(number.shortValue()); 786 } 787 if(hint == BigInteger.class) { 788 return number instanceof BigInteger ? number : 789 new BigInteger(number.toString()); 790 } 791 if(hint == BigDecimal.class) { 792 if(number instanceof BigDecimal) { 793 return number; 794 } 795 if(number instanceof BigInteger) { 796 return new BigDecimal((BigInteger)number); 797 } 798 if(number instanceof Long) { 799 // Because we can't represent long accurately as a 800 // double 801 return new BigDecimal(number.toString()); 802 } 803 return new BigDecimal(number.doubleValue()); 804 } 805 // Handle nonstandard Number subclasses as well as directly 806 // java.lang.Number too 807 if(hint.isInstance(number)) { 808 return number; 809 } 810 return null; 811 } 812 813 /** 814 * Invokes the specified method, wrapping the return value. The specialty 815 * of this method is that if the return value is null, and the return type 816 * of the invoked method is void, {@link TemplateModel#NOTHING} is returned. 817 * @param object the object to invoke the method on 818 * @param method the method to invoke 819 * @param args the arguments to the method 820 * @return the wrapped return value of the method. 821 * @throws InvocationTargetException if the invoked method threw an exception 822 * @throws IllegalAccessException if the method can't be invoked due to an 823 * access restriction. 824 * @throws TemplateModelException if the return value couldn't be wrapped 825 * (this can happen if the wrapper has an outer identity or is subclassed, 826 * and the outer identity or the subclass throws an exception. Plain 827 * BeansWrapper never throws TemplateModelException). 828 */ 829 TemplateModel invokeMethod(Object object, Method method, Object[] args) 830 throws 831 InvocationTargetException, 832 IllegalAccessException, 833 TemplateModelException 834 { 835 Object retval = method.invoke(object, args); 836 return 837 method.getReturnType() == Void.TYPE 838 ? TemplateModel.NOTHING 839 : getOuterIdentity().wrap(retval); 840 } 841 842 /** 843 * Returns a hash model that represents the so-called class static models. 844 * Every class static model is itself a hash through which you can call 845 * static methods on the specified class. To obtain a static model for a 846 * class, get the element of this hash with the fully qualified class name. 847 * For example, if you place this hash model inside the root data model 848 * under name "statics", you can use i.e. <code>statics["java.lang. 849 * System"]. currentTimeMillis()</code> to call the {@link 850 * java.lang.System#currentTimeMillis()} method. 851 * @return a hash model whose keys are fully qualified class names, and 852 * that returns hash models whose elements are the static models of the 853 * classes. 854 */ 855 public TemplateHashModel getStaticModels() 856 { 857 return staticModels; 858 } 859 860 861 /** 862 * Returns a hash model that represents the so-called class enum models. 863 * Every class' enum model is itself a hash through which you can access 864 * enum value declared by the specified class, assuming that class is an 865 * enumeration. To obtain an enum model for a class, get the element of this 866 * hash with the fully qualified class name. For example, if you place this 867 * hash model inside the root data model under name "enums", you can use 868 * i.e. <code>statics["java.math.RoundingMode"].UP</code> to access the 869 * {@link java.math.RoundingMode#UP} value. 870 * @return a hash model whose keys are fully qualified class names, and 871 * that returns hash models whose elements are the enum models of the 872 * classes. 873 * @throws UnsupportedOperationException if this method is invoked on a 874 * pre-1.5 JRE, as Java enums aren't supported there. 875 */ 876 public TemplateHashModel getEnumModels() { 877 if(enumModels == null) { 878 throw new UnsupportedOperationException( 879 "Enums not supported on pre-1.5 JRE"); 880 } 881 return enumModels; 882 } 883 884 public Object newInstance(Class clazz, List arguments) 885 throws 886 TemplateModelException 887 { 888 try 889 { 890 introspectClass(clazz); 891 Map classInfo = (Map)classCache.get(clazz); 892 Object ctors = classInfo.get(CONSTRUCTORS); 893 if(ctors == null) 894 { 895 throw new TemplateModelException("Class " + clazz.getName() + 896 " has no public constructors."); 897 } 898 Constructor ctor = null; 899 Object[] objargs; 900 if(ctors instanceof SimpleMemberModel) 901 { 902 SimpleMemberModel smm = (SimpleMemberModel)ctors; 903 ctor = (Constructor)smm.getMember(); 904 objargs = smm.unwrapArguments(arguments, this); 905 } 906 else if(ctors instanceof MethodMap) 907 { 908 MethodMap methodMap = (MethodMap)ctors; 909 MemberAndArguments maa = 910 methodMap.getMemberAndArguments(arguments); 911 objargs = maa.getArgs(); 912 ctor = (Constructor)maa.getMember(); 913 } 914 else 915 { 916 // Cannot happen 917 throw new Error(); 918 } 919 return ctor.newInstance(objargs); 920 } 921 catch (TemplateModelException e) 922 { 923 throw e; 924 } 925 catch (Exception e) 926 { 927 throw new TemplateModelException( 928 "Could not create instance of class " + clazz.getName(), e); 929 } 930 } 931 932 void introspectClass(Class clazz) 933 { 934 synchronized(classCache) 935 { 936 if(!classCache.containsKey(clazz)) 937 { 938 introspectClassInternal(clazz); 939 } 940 } 941 } 942 943 private void introspectClassInternal(Class clazz) 944 { 945 String className = clazz.getName(); 946 if(cachedClassNames.contains(className)) 947 { 948 if(logger.isInfoEnabled()) 949 { 950 logger.info("Detected a reloaded class [" + className + 951 "]. Clearing BeansWrapper caches."); 952 } 953 // Class reload detected, throw away caches 954 classCache.clear(); 955 cachedClassNames = new HashSet(); 956 synchronized(this) 957 { 958 modelCache.clearCache(); 959 } 960 staticModels.clearCache(); 961 if(enumModels != null) { 962 enumModels.clearCache(); 963 } 964 } 965 classCache.put(clazz, populateClassMap(clazz)); 966 cachedClassNames.add(className); 967 } 968 969 Map getClassKeyMap(Class clazz) 970 { 971 Map map; 972 synchronized(classCache) 973 { 974 map = (Map)classCache.get(clazz); 975 if(map == null) 976 { 977 introspectClassInternal(clazz); 978 map = (Map)classCache.get(clazz); 979 } 980 } 981 return map; 982 } 983 984 /** 985 * Returns the number of introspected methods/properties that should 986 * be available via the TemplateHashModel interface. Affected by the 987 * {@link #setMethodsShadowItems(boolean)} and {@link 988 * #setExposureLevel(int)} settings. 989 */ 990 int keyCount(Class clazz) 991 { 992 Map map = getClassKeyMap(clazz); 993 int count = map.size(); 994 if (map.containsKey(CONSTRUCTORS)) 995 count--; 996 if (map.containsKey(GENERIC_GET_KEY)) 997 count--; 998 if (map.containsKey(ARGTYPES)) 999 count--; 1000 return count; 1001 } 1002 1003 /** 1004 * Returns the Set of names of introspected methods/properties that 1005 * should be available via the TemplateHashModel interface. Affected 1006 * by the {@link #setMethodsShadowItems(boolean)} and {@link 1007 * #setExposureLevel(int)} settings. 1008 */ 1009 Set keySet(Class clazz) 1010 { 1011 Set set = new HashSet(getClassKeyMap(clazz).keySet()); 1012 set.remove(CONSTRUCTORS); 1013 set.remove(GENERIC_GET_KEY); 1014 set.remove(ARGTYPES); 1015 return set; 1016 } 1017 1018 /** 1019 * Populates a map with property and method descriptors for a specified 1020 * class. If any property or method descriptors specifies a read method 1021 * that is not accessible, replaces it with appropriate accessible method 1022 * from a superclass or interface. 1023 */ 1024 private Map populateClassMap(Class clazz) 1025 { 1026 // Populate first from bean info 1027 Map map = populateClassMapWithBeanInfo(clazz); 1028 // Next add constructors 1029 try 1030 { 1031 Constructor[] ctors = clazz.getConstructors(); 1032 if(ctors.length == 1) 1033 { 1034 Constructor ctor = ctors[0]; 1035 map.put(CONSTRUCTORS, new SimpleMemberModel(ctor, ctor.getParameterTypes())); 1036 } 1037 else if(ctors.length > 1) 1038 { 1039 MethodMap ctorMap = new MethodMap("<init>", this); 1040 for (int i = 0; i < ctors.length; i++) 1041 { 1042 ctorMap.addMember(ctors[i]); 1043 } 1044 map.put(CONSTRUCTORS, ctorMap); 1045 } 1046 } 1047 catch(SecurityException e) 1048 { 1049 logger.warn("Canont discover constructors for class " + 1050 clazz.getName(), e); 1051 } 1052 switch(map.size()) 1053 { 1054 case 0: 1055 { 1056 map = Collections12.EMPTY_MAP; 1057 break; 1058 } 1059 case 1: 1060 { 1061 Map.Entry e = (Map.Entry)map.entrySet().iterator().next(); 1062 map = Collections12.singletonMap(e.getKey(), e.getValue()); 1063 break; 1064 } 1065 } 1066 return map; 1067 } 1068 1069 private Map populateClassMapWithBeanInfo(Class clazz) 1070 { 1071 Map classMap = new HashMap(); 1072 if(exposeFields) 1073 { 1074 Field[] fields = clazz.getFields(); 1075 for (int i = 0; i < fields.length; i++) 1076 { 1077 Field field = fields[i]; 1078 if((field.getModifiers() & Modifier.STATIC) == 0) 1079 { 1080 classMap.put(field.getName(), field); 1081 } 1082 } 1083 } 1084 Map accessibleMethods = discoverAccessibleMethods(clazz); 1085 Method genericGet = (Method)accessibleMethods.get(MethodSignature.GET_STRING_SIGNATURE); 1086 if(genericGet == null) 1087 { 1088 genericGet = (Method)accessibleMethods.get(MethodSignature.GET_OBJECT_SIGNATURE); 1089 } 1090 if(genericGet != null) 1091 { 1092 classMap.put(GENERIC_GET_KEY, genericGet); 1093 } 1094 if(exposureLevel == EXPOSE_NOTHING) 1095 { 1096 return classMap; 1097 } 1098 1099 try 1100 { 1101 BeanInfo beanInfo = Introspector.getBeanInfo(clazz); 1102 PropertyDescriptor[] pda = beanInfo.getPropertyDescriptors(); 1103 MethodDescriptor[] mda = beanInfo.getMethodDescriptors(); 1104 1105 for(int i = pda.length - 1; i >= 0; --i) { 1106 PropertyDescriptor pd = pda[i]; 1107 if(pd instanceof IndexedPropertyDescriptor) { 1108 IndexedPropertyDescriptor ipd = 1109 (IndexedPropertyDescriptor)pd; 1110 Method readMethod = ipd.getIndexedReadMethod(); 1111 Method publicReadMethod = getAccessibleMethod(readMethod, 1112 accessibleMethods); 1113 if(publicReadMethod != null && isSafeMethod(publicReadMethod)) { 1114 try { 1115 if(readMethod != publicReadMethod) { 1116 ipd = new IndexedPropertyDescriptor( 1117 ipd.getName(), ipd.getReadMethod(), 1118 ipd.getWriteMethod(), publicReadMethod, 1119 ipd.getIndexedWriteMethod()); 1120 } 1121 classMap.put(ipd.getName(), ipd); 1122 getArgTypes(classMap).put(publicReadMethod, 1123 publicReadMethod.getParameterTypes()); 1124 } 1125 catch(IntrospectionException e) { 1126 logger.warn("Couldn't properly perform introspection", e); 1127 } 1128 } 1129 } 1130 else { 1131 Method readMethod = pd.getReadMethod(); 1132 Method publicReadMethod = getAccessibleMethod(readMethod, accessibleMethods); 1133 if(publicReadMethod != null && isSafeMethod(publicReadMethod)) { 1134 try { 1135 if(readMethod != publicReadMethod) { 1136 pd = new PropertyDescriptor(pd.getName(), 1137 publicReadMethod, pd.getWriteMethod()); 1138 pd.setReadMethod(publicReadMethod); 1139 } 1140 classMap.put(pd.getName(), pd); 1141 } 1142 catch(IntrospectionException e) 1143 { 1144 logger.warn("Couldn't properly perform introspection", e); 1145 } 1146 } 1147 } 1148 } 1149 if(exposureLevel < EXPOSE_PROPERTIES_ONLY) 1150 { 1151 for(int i = mda.length - 1; i >= 0; --i) 1152 { 1153 MethodDescriptor md = mda[i]; 1154 Method method = md.getMethod(); 1155 Method publicMethod = getAccessibleMethod(method, accessibleMethods); 1156 if(publicMethod != null && isSafeMethod(publicMethod)) 1157 { 1158 String name = md.getName(); 1159 Object previous = classMap.get(name); 1160 if(previous instanceof Method) 1161 { 1162 // Overloaded method - replace method with a method map 1163 MethodMap methodMap = new MethodMap(name, this); 1164 methodMap.addMember((Method)previous); 1165 methodMap.addMember(publicMethod); 1166 classMap.put(name, methodMap); 1167 // remove parameter type information 1168 getArgTypes(classMap).remove(previous); 1169 } 1170 else if(previous instanceof MethodMap) 1171 { 1172 // Already overloaded method - add new overload 1173 ((MethodMap)previous).addMember(publicMethod); 1174 } 1175 else 1176 { 1177 // Simple method (this far) 1178 classMap.put(name, publicMethod); 1179 getArgTypes(classMap).put(publicMethod, 1180 publicMethod.getParameterTypes()); 1181 } 1182 } 1183 } 1184 } 1185 return classMap; 1186 } 1187 catch(IntrospectionException e) 1188 { 1189 logger.warn("Couldn't properly perform introspection", e); 1190 return new HashMap(); 1191 } 1192 } 1193 1194 private static Map getArgTypes(Map classMap) { 1195 Map argTypes = (Map)classMap.get(ARGTYPES); 1196 if(argTypes == null) { 1197 argTypes = new HashMap(); 1198 classMap.put(ARGTYPES, argTypes); 1199 } 1200 return argTypes; 1201 } 1202 1203 static Class[] getArgTypes(Map classMap, AccessibleObject methodOrCtor) { 1204 return (Class[])((Map)classMap.get(ARGTYPES)).get(methodOrCtor); 1205 } 1206 1207 private static Method getAccessibleMethod(Method m, Map accessibles) 1208 { 1209 return m == null ? null : (Method)accessibles.get(new MethodSignature(m)); 1210 } 1211 1212 boolean isSafeMethod(Method method) 1213 { 1214 return exposureLevel < EXPOSE_SAFE || !UNSAFE_METHODS.contains(method); 1215 } 1216 1217 /** 1218 * Retrieves mapping of methods to accessible methods for a class. 1219 * In case the class is not public, retrieves methods with same 1220 * signature as its public methods from public superclasses and 1221 * interfaces (if they exist). Basically upcasts every method to the 1222 * nearest accessible method. 1223 */ 1224 private static Map discoverAccessibleMethods(Class clazz) 1225 { 1226 Map map = new HashMap(); 1227 discoverAccessibleMethods(clazz, map); 1228 return map; 1229 } 1230 1231 private static void discoverAccessibleMethods(Class clazz, Map map) 1232 { 1233 if(Modifier.isPublic(clazz.getModifiers())) 1234 { 1235 try 1236 { 1237 Method[] methods = clazz.getMethods(); 1238 for(int i = 0; i < methods.length; i++) 1239 { 1240 Method method = methods[i]; 1241 MethodSignature sig = new MethodSignature(method); 1242 map.put(sig, method); 1243 } 1244 return; 1245 } 1246 catch(SecurityException e) 1247 { 1248 logger.warn("Could not discover accessible methods of class " + 1249 clazz.getName() + 1250 ", attemping superclasses/interfaces.", e); 1251 // Fall through and attempt to discover superclass/interface 1252 // methods 1253 } 1254 } 1255 1256 Class[] interfaces = clazz.getInterfaces(); 1257 for(int i = 0; i < interfaces.length; i++) 1258 { 1259 discoverAccessibleMethods(interfaces[i], map); 1260 } 1261 Class superclass = clazz.getSuperclass(); 1262 if(superclass != null) 1263 { 1264 discoverAccessibleMethods(superclass, map); 1265 } 1266 } 1267 1268 private static final class MethodSignature 1269 { 1270 private static final MethodSignature GET_STRING_SIGNATURE = 1271 new MethodSignature("get", new Class[] { STRING_CLASS }); 1272 private static final MethodSignature GET_OBJECT_SIGNATURE = 1273 new MethodSignature("get", new Class[] { OBJECT_CLASS }); 1274 1275 private final String name; 1276 private final Class[] args; 1277 1278 private MethodSignature(String name, Class[] args) 1279 { 1280 this.name = name; 1281 this.args = args; 1282 } 1283 1284 MethodSignature(Method method) 1285 { 1286 this(method.getName(), method.getParameterTypes()); 1287 } 1288 1289 public boolean equals(Object o) 1290 { 1291 if(o instanceof MethodSignature) 1292 { 1293 MethodSignature ms = (MethodSignature)o; 1294 return ms.name.equals(name) && Arrays.equals(args, ms.args); 1295 } 1296 return false; 1297 } 1298 1299 public int hashCode() 1300 { 1301 return name.hashCode() ^ args.length; 1302 } 1303 } 1304 1305 private static final Set createUnsafeMethodsSet() 1306 { 1307 Properties props = new Properties(); 1308 InputStream in = BeansWrapper.class.getResourceAsStream("unsafeMethods.txt"); 1309 if(in != null) 1310 { 1311 String methodSpec = null; 1312 try 1313 { 1314 try 1315 { 1316 props.load(in); 1317 } 1318 finally 1319 { 1320 in.close(); 1321 } 1322 Set set = new HashSet(props.size() * 4/3, .75f); 1323 Map primClasses = createPrimitiveClassesMap(); 1324 for (Iterator iterator = props.keySet().iterator(); iterator.hasNext();) 1325 { 1326 methodSpec = (String) iterator.next(); 1327 try { 1328 set.add(parseMethodSpec(methodSpec, primClasses)); 1329 } 1330 catch(ClassNotFoundException e) { 1331 if(DEVELOPMENT) { 1332 throw e; 1333 } 1334 } 1335 catch(NoSuchMethodException e) { 1336 if(DEVELOPMENT) { 1337 throw e; 1338 } 1339 } 1340 } 1341 return set; 1342 } 1343 catch(Exception e) 1344 { 1345 throw new RuntimeException("Could not load unsafe method " + methodSpec + " " + e.getClass().getName() + " " + e.getMessage()); 1346 } 1347 } 1348 return Collections.EMPTY_SET; 1349 } 1350 1351 private static Method parseMethodSpec(String methodSpec, Map primClasses) 1352 throws 1353 ClassNotFoundException, 1354 NoSuchMethodException 1355 { 1356 int brace = methodSpec.indexOf('('); 1357 int dot = methodSpec.lastIndexOf('.', brace); 1358 Class clazz = ClassUtil.forName(methodSpec.substring(0, dot)); 1359 String methodName = methodSpec.substring(dot + 1, brace); 1360 String argSpec = methodSpec.substring(brace + 1, methodSpec.length() - 1); 1361 StringTokenizer tok = new StringTokenizer(argSpec, ","); 1362 int argcount = tok.countTokens(); 1363 Class[] argTypes = new Class[argcount]; 1364 for (int i = 0; i < argcount; i++) 1365 { 1366 String argClassName = tok.nextToken(); 1367 argTypes[i] = (Class)primClasses.get(argClassName); 1368 if(argTypes[i] == null) 1369 { 1370 argTypes[i] = ClassUtil.forName(argClassName); 1371 } 1372 } 1373 return clazz.getMethod(methodName, argTypes); 1374 } 1375 1376 private static Map createPrimitiveClassesMap() 1377 { 1378 Map map = new HashMap(); 1379 map.put("boolean", Boolean.TYPE); 1380 map.put("byte", Byte.TYPE); 1381 map.put("char", Character.TYPE); 1382 map.put("short", Short.TYPE); 1383 map.put("int", Integer.TYPE); 1384 map.put("long", Long.TYPE); 1385 map.put("float", Float.TYPE); 1386 map.put("double", Double.TYPE); 1387 return map; 1388 } 1389 1390 1391 /** 1392 * Converts any {@link BigDecimal}s in the passed array to the type of 1393 * the corresponding formal argument of the method. 1394 */ 1395 public static void coerceBigDecimals(AccessibleObject callable, Object[] args) 1396 { 1397 Class[] formalTypes = null; 1398 for(int i = 0; i < args.length; ++i) { 1399 Object arg = args[i]; 1400 if(arg instanceof BigDecimal) { 1401 if(formalTypes == null) { 1402 if(callable instanceof Method) { 1403 formalTypes = ((Method)callable).getParameterTypes(); 1404 } 1405 else if(callable instanceof Constructor) { 1406 formalTypes = ((Constructor)callable).getParameterTypes(); 1407 } 1408 else { 1409 throw new IllegalArgumentException("Expected method or " 1410 + " constructor; callable is " + 1411 callable.getClass().getName()); 1412 } 1413 } 1414 args[i] = coerceBigDecimal((BigDecimal)arg, formalTypes[i]); 1415 } 1416 } 1417 } 1418 1419 /** 1420 * Converts any {@link BigDecimal}s in the passed array to the type of 1421 * the corresponding formal argument of the method. 1422 */ 1423 public static void coerceBigDecimals(Class[] formalTypes, Object[] args) 1424 { 1425 int typeLen = formalTypes.length; 1426 int argsLen = args.length; 1427 int min = Math.min(typeLen, argsLen); 1428 for(int i = 0; i < min; ++i) { 1429 Object arg = args[i]; 1430 if(arg instanceof BigDecimal) { 1431 args[i] = coerceBigDecimal((BigDecimal)arg, formalTypes[i]); 1432 } 1433 } 1434 if(argsLen > typeLen) { 1435 Class varArgType = formalTypes[typeLen - 1]; 1436 for(int i = typeLen; i < argsLen; ++i) { 1437 Object arg = args[i]; 1438 if(arg instanceof BigDecimal) { 1439 args[i] = coerceBigDecimal((BigDecimal)arg, varArgType); 1440 } 1441 } 1442 } 1443 } 1444 1445 public static Object coerceBigDecimal(BigDecimal bd, Class formalType) { 1446 // int is expected in most situations, so we check it first 1447 if(formalType == Integer.TYPE || formalType == Integer.class) { 1448 return new Integer(bd.intValue()); 1449 } 1450 else if(formalType == Double.TYPE || formalType == Double.class) { 1451 return new Double(bd.doubleValue()); 1452 } 1453 else if(formalType == Long.TYPE || formalType == Long.class) { 1454 return new Long(bd.longValue()); 1455 } 1456 else if(formalType == Float.TYPE || formalType == Float.class) { 1457 return new Float(bd.floatValue()); 1458 } 1459 else if(formalType == Short.TYPE || formalType == Short.class) { 1460 return new Short(bd.shortValue()); 1461 } 1462 else if(formalType == Byte.TYPE || formalType == Byte.class) { 1463 return new Byte(bd.byteValue()); 1464 } 1465 else if(BIGINTEGER_CLASS.isAssignableFrom(formalType)) { 1466 return bd.toBigInteger(); 1467 } 1468 return bd; 1469 } 1470 1471 private static ClassBasedModelFactory createEnumModels(BeansWrapper wrapper) { 1472 if(ENUMS_MODEL_CTOR != null) { 1473 try { 1474 return (ClassBasedModelFactory)ENUMS_MODEL_CTOR.newInstance( 1475 new Object[] { wrapper }); 1476 } catch(Exception e) { 1477 throw new UndeclaredThrowableException(e); 1478 } 1479 } else { 1480 return null; 1481 } 1482 } 1483 1484 private static Constructor enumsModelCtor() { 1485 try { 1486 // Check if Enums are available on this platform 1487 Class.forName("java.lang.Enum"); 1488 // If they are, return the appropriate constructor for enum models 1489 return Class.forName( 1490 "freemarker.ext.beans.EnumModels").getDeclaredConstructor( 1491 new Class[] { BeansWrapper.class }); 1492 } 1493 catch(Exception e) { 1494 // Otherwise, return null 1495 return null; 1496 } 1497 } 1498 }