| 1 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.Constructor ; 64 import java.lang.reflect.InvocationTargetException ; 65 import java.lang.reflect.Method ; 66 import java.lang.reflect.Modifier ; 67 import java.math.BigDecimal ; 68 import java.util.Arrays ; 69 import java.util.Collection ; 70 import java.util.Collections ; 71 import java.util.Date ; 72 import java.util.Enumeration ; 73 import java.util.HashMap ; 74 import java.util.HashSet ; 75 import java.util.Iterator ; 76 import java.util.List ; 77 import java.util.Map ; 78 import java.util.Properties ; 79 import java.util.ResourceBundle ; 80 import java.util.Set ; 81 import java.util.StringTokenizer ; 82 83 import freemarker.ext.util.ModelCache; 84 import freemarker.ext.util.ModelFactory; 85 import freemarker.ext.util.WrapperTemplateModel; 86 import freemarker.log.Logger; 87 import freemarker.template.AdapterTemplateModel; 88 import freemarker.template.ObjectWrapper; 89 import freemarker.template.TemplateBooleanModel; 90 import freemarker.template.TemplateCollectionModel; 91 import freemarker.template.TemplateDateModel; 92 import freemarker.template.TemplateHashModel; 93 import freemarker.template.TemplateModel; 94 import freemarker.template.TemplateModelAdapter; 95 import freemarker.template.TemplateModelException; 96 import freemarker.template.TemplateNumberModel; 97 import freemarker.template.TemplateScalarModel; 98 import freemarker.template.TemplateSequenceModel; 99 import freemarker.template.utility.ClassUtil; 100 import freemarker.template.utility.Collections12; 101 import freemarker.template.utility.SecurityUtilities; 102 103 109 public class BeansWrapper implements ObjectWrapper 110 { 111 private static final Class BIGINTEGER_CLASS = java.math.BigInteger .class; 112 private static final Class BOOLEAN_CLASS = Boolean .class; 113 private static final Class CHARACTER_CLASS = Character .class; 114 private static final Class COLLECTION_CLASS = Collection .class; 115 private static final Class DATE_CLASS = Date .class; 116 private static final Class LIST_CLASS = List .class; 117 private static final Class MAP_CLASS = Map .class; 118 private static final Class NUMBER_CLASS = Number .class; 119 private static final Class OBJECT_CLASS = Object .class; 120 private static final Class SET_CLASS = Set .class; 121 private static final Class STRING_CLASS = String .class; 122 private static final Class TEMPLATE_MODEL_CLASS = TemplateModel.class; 123 124 private static final boolean DEVELOPMENT = "true".equals(SecurityUtilities.getSystemProperty("freemarker.development")); 128 129 private static final Logger logger = Logger.getLogger("freemarker.beans"); 130 131 private static final Set UNSAFE_METHODS = createUnsafeMethodsSet(); 132 133 static final Object GENERIC_GET_KEY = new Object (); 134 private static final Object CONSTRUCTORS = new Object (); 135 private static final Object ARGTYPES = new Object (); 136 137 140 private static final BeansWrapper INSTANCE = new BeansWrapper(); 141 142 private final Map classCache = new HashMap (); 147 private Set cachedClassNames = new HashSet (); 148 149 private final StaticModels staticModels = new StaticModels(this); 150 151 private final ModelCache modelCache = new ModelCache(this); 152 153 private final BooleanModel FALSE = new BooleanModel(Boolean.FALSE, this); 154 private final BooleanModel TRUE = new BooleanModel(Boolean.TRUE, this); 155 156 160 public static final int EXPOSE_ALL = 0; 161 162 173 public static final int EXPOSE_SAFE = 1; 174 175 180 public static final int EXPOSE_PROPERTIES_ONLY = 2; 181 182 190 public static final int EXPOSE_NOTHING = 3; 191 192 private int exposureLevel = EXPOSE_SAFE; 193 private TemplateModel nullModel = null; 194 private boolean methodsShadowItems = true; 195 private int defaultDateType = TemplateDateModel.UNKNOWN; 196 197 private ObjectWrapper outerIdentity = this; 198 private boolean simpleMapWrapper; 199 200 206 public BeansWrapper() 207 { 208 } 209 210 219 public void setOuterIdentity(ObjectWrapper outerIdentity) 220 { 221 this.outerIdentity = outerIdentity; 222 } 223 224 228 public ObjectWrapper getOuterIdentity() 229 { 230 return outerIdentity; 231 } 232 233 243 public void setSimpleMapWrapper(boolean simpleMapWrapper) 244 { 245 this.simpleMapWrapper = simpleMapWrapper; 246 } 247 248 public boolean isSimpleMapWrapper() 249 { 250 return simpleMapWrapper; 251 } 252 253 258 public void setExposureLevel(int exposureLevel) 259 { 260 if(exposureLevel < EXPOSE_ALL || exposureLevel > EXPOSE_NOTHING) 261 { 262 throw new IllegalArgumentException ("Illegal exposure level " + exposureLevel); 263 } 264 this.exposureLevel = exposureLevel; 265 } 266 267 int getExposureLevel() 268 { 269 return exposureLevel; 270 } 271 272 282 public synchronized void setMethodsShadowItems(boolean methodsShadowItems) 283 { 284 this.methodsShadowItems = methodsShadowItems; 285 } 286 287 boolean isMethodsShadowItems() 288 { 289 return methodsShadowItems; 290 } 291 292 299 public synchronized void setDefaultDateType(int defaultDateType) { 300 this.defaultDateType = defaultDateType; 301 } 302 303 protected int getDefaultDateType() { 304 return defaultDateType; 305 } 306 307 313 public void setUseCache(boolean useCache) 314 { 315 modelCache.setUseCache(useCache); 316 } 317 318 326 public void setNullModel(TemplateModel nullModel) 327 { 328 this.nullModel = nullModel; 329 } 330 331 342 public static final BeansWrapper getDefaultInstance() 343 { 344 return INSTANCE; 345 } 346 347 367 public TemplateModel wrap(Object object) throws TemplateModelException 368 { 369 if(object == null) 370 return nullModel; 371 if(object instanceof TemplateModel) 372 return (TemplateModel)object; 373 if(object instanceof TemplateModelAdapter) 374 return ((TemplateModelAdapter)object).getTemplateModel(); 375 if(object instanceof Map ) 376 return modelCache.getInstance(object, simpleMapWrapper ? SimpleMapModel.FACTORY : MapModel.FACTORY); 377 if(object instanceof Collection ) 378 return modelCache.getInstance(object, CollectionModel.FACTORY); 379 if(object.getClass().isArray()) 380 return modelCache.getInstance(object, ArrayModel.FACTORY); 381 if(object instanceof Number ) 382 return modelCache.getInstance(object, NumberModel.FACTORY); 383 if(object instanceof Date ) 384 return modelCache.getInstance(object, DateModel.FACTORY); 385 if(object instanceof Boolean ) 386 return ((Boolean )object).booleanValue() ? TRUE : FALSE; 387 if(object instanceof ResourceBundle ) 388 return modelCache.getInstance(object, ResourceBundleModel.FACTORY); 389 if(object instanceof Iterator ) 390 return new IteratorModel((Iterator )object, this); 391 if(object instanceof Enumeration ) 392 return new EnumerationModel((Enumeration )object, this); 393 return modelCache.getInstance(object, StringModel.FACTORY); 394 } 395 396 protected TemplateModel getInstance(Object object, ModelFactory factory) 397 { 398 return modelCache.getInstance(object, factory); 399 } 400 401 protected TemplateModel create(Object object, Object factory) 402 { 403 return ((ModelFactory)factory).create(object, this); 404 } 405 406 415 public Object unwrap(TemplateModel model) throws TemplateModelException 416 { 417 return unwrap(model, OBJECT_CLASS); 418 } 419 420 public Object unwrap(TemplateModel model, Class hint) 421 throws TemplateModelException 422 { 423 if(model == nullModel) { 424 return null; 425 } 426 427 if(TEMPLATE_MODEL_CLASS.isAssignableFrom(hint)) { 430 return model; 431 } 432 433 if(model instanceof AdapterTemplateModel) { 438 return ((AdapterTemplateModel)model).getAdaptedObject(hint); 439 } 440 if(model instanceof WrapperTemplateModel) { 441 return ((WrapperTemplateModel)model).getWrappedObject(); 442 } 443 444 449 if(STRING_CLASS == hint) { 450 if(model instanceof TemplateScalarModel) { 451 return ((TemplateScalarModel)model).getAsString(); 452 } 453 } 454 boolean isBoolean = Boolean.TYPE == hint; 455 if(isBoolean || BOOLEAN_CLASS == hint) { 456 if(model instanceof TemplateBooleanModel) { 457 return ((TemplateBooleanModel)model).getAsBoolean() ? Boolean.TRUE : Boolean.FALSE; 458 } 459 } 460 if(MAP_CLASS == hint) { 461 if(model instanceof TemplateHashModel) { 462 return new HashAdapter((TemplateHashModel)model, this); 463 } 464 } 465 if(LIST_CLASS == hint) { 466 if(model instanceof TemplateSequenceModel) { 467 return new SequenceAdapter((TemplateSequenceModel)model, this); 468 } 469 } 470 if(SET_CLASS == hint || COLLECTION_CLASS == hint) { 471 if(model instanceof TemplateCollectionModel) { 472 return new CollectionAdapter((TemplateCollectionModel)model, this); 473 } 474 } 475 476 boolean isChar = hint == Character.TYPE; 478 if(isChar || hint == CHARACTER_CLASS) { 479 if(model instanceof TemplateScalarModel) { 480 String s = ((TemplateScalarModel)model).getAsString(); 481 if(s.length() == 1) { 482 return new Character (s.charAt(0)); 483 } 484 } 485 } 486 487 if((hint.isPrimitive() && !isChar && !isBoolean) 489 || NUMBER_CLASS.isAssignableFrom(hint)) { 490 if(model instanceof TemplateNumberModel) { 491 return ((TemplateNumberModel)model).getAsNumber(); 492 } 493 } 494 if(DATE_CLASS.isAssignableFrom(hint)) { 495 if(model instanceof TemplateDateModel) { 496 return ((TemplateDateModel)model).getAsDate(); 497 } 498 } 499 500 if(model instanceof TemplateNumberModel) 503 return ((TemplateNumberModel)model).getAsNumber(); 504 if(model instanceof TemplateDateModel) 505 return ((TemplateDateModel)model).getAsDate(); 506 if(model instanceof TemplateScalarModel) 507 return ((TemplateScalarModel)model).getAsString(); 508 if(model instanceof TemplateBooleanModel) 509 return ((TemplateBooleanModel)model).getAsBoolean() ? Boolean.TRUE : Boolean.FALSE; 510 if(model instanceof TemplateHashModel) 511 return new HashAdapter((TemplateHashModel)model, this); 512 if(model instanceof TemplateSequenceModel) 513 return new SequenceAdapter((TemplateSequenceModel)model, this); 514 if(model instanceof TemplateCollectionModel) 515 return new CollectionAdapter((TemplateCollectionModel)model, this); 516 517 return model; 521 } 522 523 531 Object [] unwrapArguments(List arguments, Class [] argTypes) throws 532 TemplateModelException 533 { 534 Object [] args = null; 535 if(arguments != null) { 536 int size = arguments.size(); 537 args = new Object [size]; 538 Iterator it = arguments.iterator(); 539 for(int i = 0; it.hasNext(); ++i) { 540 args[i] = unwrap((TemplateModel)it.next(), argTypes[i]); 541 } 542 } 543 return args; 544 } 545 546 Object [] unwrapArguments(List arguments) throws TemplateModelException 547 { 548 Object [] args = null; 549 if(arguments != null) { 550 int size = arguments.size(); 551 args = new Object [size]; 552 Iterator it = arguments.iterator(); 553 int i = 0; 554 while(it.hasNext()) { 555 args[i++] = unwrap((TemplateModel)it.next()); 556 } 557 } 558 return args; 559 } 560 561 577 TemplateModel invokeMethod(Object object, Method method, Object [] args) 578 throws 579 InvocationTargetException , 580 IllegalAccessException , 581 TemplateModelException 582 { 583 Object retval = method.invoke(object, args); 584 return 585 method.getReturnType() == Void.TYPE 586 ? TemplateModel.NOTHING 587 : getOuterIdentity().wrap(retval); 588 } 589 590 603 public TemplateHashModel getStaticModels() 604 { 605 return staticModels; 606 } 607 608 public Object newInstance(Class clazz, List arguments) 609 throws 610 TemplateModelException 611 { 612 try 613 { 614 introspectClass(clazz); 615 Map classInfo = (Map )classCache.get(clazz); 616 Object ctors = classInfo.get(CONSTRUCTORS); 617 if(ctors == null) 618 { 619 throw new TemplateModelException("Class " + clazz.getName() + 620 " has no public constructors."); 621 } 622 Constructor ctor = null; 623 Object [] objargs; 624 if(ctors instanceof Constructor ) 625 { 626 ctor = (Constructor )ctors; 627 objargs = unwrapArguments(arguments, getArgTypes(classInfo, ctor)); 628 } 629 else if(ctors instanceof MethodMap) 630 { 631 MethodMap methodMap = (MethodMap)ctors; 632 objargs = unwrapArguments(arguments, methodMap.getUnwrapTypes(arguments)); 633 ctor = (Constructor )methodMap.getMostSpecific(objargs); 634 } 635 else 636 { 637 throw new Error (); 639 } 640 if(objargs != null) { 641 coerceBigDecimals(ctor, objargs); 642 } 643 return ctor.newInstance(objargs); 644 } 645 catch (TemplateModelException e) 646 { 647 throw e; 648 } 649 catch (Exception e) 650 { 651 throw new TemplateModelException( 652 "Could not create instance of class " + clazz.getName(), e); 653 } 654 } 655 656 void introspectClass(Class clazz) 657 { 658 synchronized(classCache) 659 { 660 if(!classCache.containsKey(clazz)) 661 { 662 String className = clazz.getName(); 663 if(cachedClassNames.contains(className)) 664 { 665 if(logger.isInfoEnabled()) 666 { 667 logger.info("Detected a reloaded class [" + className + 668 "]. Clearing BeansWrapper caches."); 669 } 670 classCache.clear(); 672 cachedClassNames = new HashSet (); 673 synchronized(this) 674 { 675 modelCache.clearCache(); 676 } 677 staticModels.clearCache(); 678 } 679 classCache.put(clazz, populateClassMap(clazz)); 680 cachedClassNames.add(className); 681 } 682 } 683 } 684 685 Map getClassKeyMap(Class clazz) 686 { 687 synchronized(classCache) 688 { 689 return (Map )classCache.get(clazz); 690 } 691 } 692 693 699 int keyCount(Class clazz) 700 { 701 Map map = getClassKeyMap(clazz); 702 int count = map.size(); 703 if (map.containsKey(CONSTRUCTORS)) 704 count--; 705 if (map.containsKey(GENERIC_GET_KEY)) 706 count--; 707 if (map.containsKey(ARGTYPES)) 708 count--; 709 return count; 710 } 711 712 718 Set keySet(Class clazz) 719 { 720 Set set = new HashSet (getClassKeyMap(clazz).keySet()); 721 set.remove(CONSTRUCTORS); 722 set.remove(GENERIC_GET_KEY); 723 set.remove(ARGTYPES); 724 return set; 725 } 726 727 733 private Map populateClassMap(Class clazz) 734 { 735 Map map = populateClassMapWithBeanInfo(clazz); 737 try 739 { 740 Constructor [] ctors = clazz.getConstructors(); 741 if(ctors.length == 1) 742 { 743 Constructor ctor = ctors[0]; 744 map.put(CONSTRUCTORS, ctor); 745 getArgTypes(map).put(ctor, ctor.getParameterTypes()); 746 } 747 else if(ctors.length > 1) 748 { 749 MethodMap ctorMap = new MethodMap("<init>"); 750 for (int i = 0; i < ctors.length; i++) 7
|