Source Code Cross Referenced for BeansWrapper.java in  » Template-Engine » freemarker-2.3.10 » freemarker » ext » beans » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Template Engine » freemarker 2.3.10 » freemarker.ext.beans 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /*
0002:         * Copyright (c) 2003 The Visigoth Software Society. All rights
0003:         * reserved.
0004:         *
0005:         * Redistribution and use in source and binary forms, with or without
0006:         * modification, are permitted provided that the following conditions
0007:         * are met:
0008:         *
0009:         * 1. Redistributions of source code must retain the above copyright
0010:         *    notice, this list of conditions and the following disclaimer.
0011:         *
0012:         * 2. Redistributions in binary form must reproduce the above copyright
0013:         *    notice, this list of conditions and the following disclaimer in
0014:         *    the documentation and/or other materials provided with the
0015:         *    distribution.
0016:         *
0017:         * 3. The end-user documentation included with the redistribution, if
0018:         *    any, must include the following acknowledgement:
0019:         *       "This product includes software developed by the
0020:         *        Visigoth Software Society (http://www.visigoths.org/)."
0021:         *    Alternately, this acknowledgement may appear in the software itself,
0022:         *    if and wherever such third-party acknowledgements normally appear.
0023:         *
0024:         * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the 
0025:         *    project contributors may be used to endorse or promote products derived
0026:         *    from this software without prior written permission. For written
0027:         *    permission, please contact visigoths@visigoths.org.
0028:         *
0029:         * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
0030:         *    nor may "FreeMarker" or "Visigoth" appear in their names
0031:         *    without prior written permission of the Visigoth Software Society.
0032:         *
0033:         * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0034:         * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0035:         * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0036:         * DISCLAIMED.  IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
0037:         * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0038:         * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0039:         * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0040:         * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0041:         * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0042:         * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0043:         * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0044:         * SUCH DAMAGE.
0045:         * ====================================================================
0046:         *
0047:         * This software consists of voluntary contributions made by many
0048:         * individuals on behalf of the Visigoth Software Society. For more
0049:         * information on the Visigoth Software Society, please see
0050:         * http://www.visigoths.org/
0051:         */
0052:
0053:        package freemarker.ext.beans;
0054:
0055:        import java.beans.BeanInfo;
0056:        import java.beans.IndexedPropertyDescriptor;
0057:        import java.beans.IntrospectionException;
0058:        import java.beans.Introspector;
0059:        import java.beans.MethodDescriptor;
0060:        import java.beans.PropertyDescriptor;
0061:        import java.io.InputStream;
0062:        import java.lang.reflect.AccessibleObject;
0063:        import java.lang.reflect.Array;
0064:        import java.lang.reflect.Constructor;
0065:        import java.lang.reflect.Field;
0066:        import java.lang.reflect.InvocationTargetException;
0067:        import java.lang.reflect.Method;
0068:        import java.lang.reflect.Modifier;
0069:        import java.math.BigDecimal;
0070:        import java.math.BigInteger;
0071:        import java.util.Arrays;
0072:        import java.util.Collection;
0073:        import java.util.Collections;
0074:        import java.util.Date;
0075:        import java.util.Enumeration;
0076:        import java.util.HashMap;
0077:        import java.util.HashSet;
0078:        import java.util.Iterator;
0079:        import java.util.List;
0080:        import java.util.Map;
0081:        import java.util.Properties;
0082:        import java.util.ResourceBundle;
0083:        import java.util.Set;
0084:        import java.util.StringTokenizer;
0085:
0086:        import freemarker.ext.util.IdentityHashMap;
0087:        import freemarker.ext.util.ModelCache;
0088:        import freemarker.ext.util.ModelFactory;
0089:        import freemarker.ext.util.WrapperTemplateModel;
0090:        import freemarker.log.Logger;
0091:        import freemarker.template.AdapterTemplateModel;
0092:        import freemarker.template.ObjectWrapper;
0093:        import freemarker.template.TemplateBooleanModel;
0094:        import freemarker.template.TemplateCollectionModel;
0095:        import freemarker.template.TemplateDateModel;
0096:        import freemarker.template.TemplateHashModel;
0097:        import freemarker.template.TemplateModel;
0098:        import freemarker.template.TemplateModelAdapter;
0099:        import freemarker.template.TemplateModelException;
0100:        import freemarker.template.TemplateNumberModel;
0101:        import freemarker.template.TemplateScalarModel;
0102:        import freemarker.template.TemplateSequenceModel;
0103:        import freemarker.template.utility.ClassUtil;
0104:        import freemarker.template.utility.Collections12;
0105:        import freemarker.template.utility.SecurityUtilities;
0106:        import freemarker.template.utility.UndeclaredThrowableException;
0107:
0108:        /**
0109:         * Utility class that provides generic services to reflection classes.
0110:         * It handles all polymorphism issues in the {@link #wrap(Object)} and {@link #unwrap(TemplateModel)} methods.
0111:         * @author Attila Szegedi
0112:         * @version $Id: BeansWrapper.java,v 1.91.2.13 2007/04/02 13:08:59 szegedia Exp $
0113:         */
0114:        public class BeansWrapper implements  ObjectWrapper {
0115:            private static final Class BIGINTEGER_CLASS = java.math.BigInteger.class;
0116:            private static final Class BOOLEAN_CLASS = Boolean.class;
0117:            private static final Class CHARACTER_CLASS = Character.class;
0118:            private static final Class COLLECTION_CLASS = Collection.class;
0119:            private static final Class DATE_CLASS = Date.class;
0120:            private static final Class HASHADAPTER_CLASS = HashAdapter.class;
0121:            private static final Class ITERABLE_CLASS;
0122:            private static final Class LIST_CLASS = List.class;
0123:            private static final Class MAP_CLASS = Map.class;
0124:            private static final Class NUMBER_CLASS = Number.class;
0125:            private static final Class OBJECT_CLASS = Object.class;
0126:            private static final Class SEQUENCEADAPTER_CLASS = SequenceAdapter.class;
0127:            private static final Class SET_CLASS = Set.class;
0128:            private static final Class SETADAPTER_CLASS = SetAdapter.class;
0129:            private static final Class STRING_CLASS = String.class;
0130:            static {
0131:                Class iterable;
0132:                try {
0133:                    iterable = Class.forName("java.lang.Iterable");
0134:                } catch (ClassNotFoundException e) {
0135:                    // We're running on a pre-1.5 JRE
0136:                    iterable = null;
0137:                }
0138:                ITERABLE_CLASS = iterable;
0139:            }
0140:
0141:            // When this property is true, some things are stricter. This is mostly to
0142:            // catch anomalous things in development that can otherwise be valid situations
0143:            // for our users.
0144:            private static final boolean DEVELOPMENT = "true"
0145:                    .equals(SecurityUtilities
0146:                            .getSystemProperty("freemarker.development"));
0147:
0148:            private static final Constructor ENUMS_MODEL_CTOR = enumsModelCtor();
0149:
0150:            private static final Logger logger = Logger
0151:                    .getLogger("freemarker.beans");
0152:
0153:            private static final Set UNSAFE_METHODS = createUnsafeMethodsSet();
0154:
0155:            static final Object GENERIC_GET_KEY = new Object();
0156:            private static final Object CONSTRUCTORS = new Object();
0157:            private static final Object ARGTYPES = new Object();
0158:
0159:            /**
0160:             * The default instance of BeansWrapper
0161:             */
0162:            private static final BeansWrapper INSTANCE = new BeansWrapper();
0163:
0164:            // Cache of hash maps that contain already discovered properties and methods
0165:            // for a specified class. Each key is a Class, each value is a hash map. In
0166:            // that hash map, each key is a property/method name, each value is a
0167:            // MethodDescriptor or a PropertyDescriptor assigned to that property/method.
0168:            private final Map classCache = new HashMap();
0169:            private Set cachedClassNames = new HashSet();
0170:
0171:            private final StaticModels staticModels = new StaticModels(this );
0172:            private final ClassBasedModelFactory enumModels = createEnumModels(this );
0173:
0174:            private final ModelCache modelCache = new ModelCache(this );
0175:
0176:            private final BooleanModel FALSE = new BooleanModel(Boolean.FALSE,
0177:                    this );
0178:            private final BooleanModel TRUE = new BooleanModel(Boolean.TRUE,
0179:                    this );
0180:
0181:            /**
0182:             * At this level of exposure, all methods and properties of the
0183:             * wrapped objects are exposed to the template.
0184:             */
0185:            public static final int EXPOSE_ALL = 0;
0186:
0187:            /**
0188:             * At this level of exposure, all methods and properties of the wrapped
0189:             * objects are exposed to the template except methods that are deemed
0190:             * not safe. The not safe methods are java.lang.Object methods wait() and
0191:             * notify(), java.lang.Class methods getClassLoader() and newInstance(),
0192:             * java.lang.reflect.Method and java.lang.reflect.Constructor invoke() and
0193:             * newInstance() methods, all java.lang.reflect.Field set methods, all 
0194:             * java.lang.Thread and java.lang.ThreadGroup methods that can change its 
0195:             * state, as well as the usual suspects in java.lang.System and
0196:             * java.lang.Runtime.
0197:             */
0198:            public static final int EXPOSE_SAFE = 1;
0199:
0200:            /**
0201:             * At this level of exposure, only property getters are exposed.
0202:             * Additionally, property getters that map to unsafe methods are not
0203:             * exposed (i.e. Class.classLoader and Thread.contextClassLoader).
0204:             */
0205:            public static final int EXPOSE_PROPERTIES_ONLY = 2;
0206:
0207:            /**
0208:             * At this level of exposure, no bean properties and methods are exposed.
0209:             * Only map items, resource bundle items, and objects retrieved through
0210:             * the generic get method (on objects of classes that have a generic get
0211:             * method) can be retrieved through the hash interface. You might want to 
0212:             * call {@link #setMethodsShadowItems(boolean)} with <tt>false</tt> value to
0213:             * speed up map item retrieval.
0214:             */
0215:            public static final int EXPOSE_NOTHING = 3;
0216:
0217:            private int exposureLevel = EXPOSE_SAFE;
0218:            private TemplateModel nullModel = null;
0219:            private boolean methodsShadowItems = true;
0220:            private boolean exposeFields = false;
0221:            private int defaultDateType = TemplateDateModel.UNKNOWN;
0222:
0223:            private ObjectWrapper outerIdentity = this ;
0224:            private boolean simpleMapWrapper;
0225:            private boolean strict = false;
0226:
0227:            /**
0228:             * Creates a new instance of BeansWrapper. The newly created instance
0229:             * will use the null reference as its null object, it will use
0230:             * {@link #EXPOSE_SAFE} method exposure level, and will not cache
0231:             * model instances.
0232:             */
0233:            public BeansWrapper() {
0234:            }
0235:
0236:            /**
0237:             * @see #setStrict(boolean)
0238:             */
0239:            public boolean isStrict() {
0240:                return strict;
0241:            }
0242:
0243:            /**
0244:             * Specifies if an attempt to read a bean property that doesn't exist in the
0245:             * wrapped object should throw an {@link InvalidPropertyException}.
0246:             * 
0247:             * <p>If this property is <tt>false</tt> (the default) then an attempt to read
0248:             * a missing bean property is the same as reading an existing bean property whose
0249:             * value is <tt>null</tt>. The template can't tell the difference, and thus always
0250:             * can use <tt>?default('something')</tt> and <tt>?exists</tt> and similar built-ins
0251:             * to handle the situation.
0252:             *
0253:             * <p>If this property is <tt>true</tt> then an attempt to read a bean propertly in
0254:             * the template (like <tt>myBean.aProperty</tt>) that doesn't exist in the bean
0255:             * object (as opposed to just holding <tt>null</tt> value) will cause
0256:             * {@link InvalidPropertyException}, which can't be suppressed in the template
0257:             * (not even with <tt>myBean.noSuchProperty?default('something')</tt>). This way
0258:             * <tt>?default('something')</tt> and <tt>?exists</tt> and similar built-ins can be used to
0259:             * handle existing properties whose value is <tt>null</tt>, without the risk of
0260:             * hiding typos in the property names. Typos will always cause error. But mind you, it
0261:             * goes against the basic approach of FreeMarker, so use this feature only if you really
0262:             * know what are you doing.
0263:             */
0264:            public void setStrict(boolean strict) {
0265:                this .strict = strict;
0266:            }
0267:
0268:            /**
0269:             * When wrapping an object, the BeansWrapper commonly needs to wrap
0270:             * "sub-objects", for example each element in a wrapped collection.
0271:             * Normally it wraps these objects using itself. However, this makes
0272:             * it difficult to delegate to a BeansWrapper as part of a custom
0273:             * aggregate ObjectWrapper. This method lets you set the ObjectWrapper
0274:             * which will be used to wrap the sub-objects.
0275:             * @param outerIdentity the aggregate ObjectWrapper
0276:             */
0277:            public void setOuterIdentity(ObjectWrapper outerIdentity) {
0278:                this .outerIdentity = outerIdentity;
0279:            }
0280:
0281:            /**
0282:             * By default returns <tt>this</tt>.
0283:             * @see #setOuterIdentity(ObjectWrapper)
0284:             */
0285:            public ObjectWrapper getOuterIdentity() {
0286:                return outerIdentity;
0287:            }
0288:
0289:            /**
0290:             * By default the BeansWrapper wraps classes implementing
0291:             * java.util.Map using {@link MapModel}. Setting this flag will
0292:             * cause it to use a {@link SimpleMapModel} instead. The biggest
0293:             * difference is that when using a {@link SimpleMapModel}, the
0294:             * map will be visible as <code>TemplateHashModelEx</code>,
0295:             * and the subvariables will be the content of the map,
0296:             * without the other methods and properties of the map object.
0297:             * @param simpleMapWrapper enable simple map wrapping
0298:             */
0299:            public void setSimpleMapWrapper(boolean simpleMapWrapper) {
0300:                this .simpleMapWrapper = simpleMapWrapper;
0301:            }
0302:
0303:            public boolean isSimpleMapWrapper() {
0304:                return simpleMapWrapper;
0305:            }
0306:
0307:            /**
0308:             * Sets the method exposure level. By default, set to <code>EXPOSE_SAFE</code>.
0309:             * @param exposureLevel can be any of the <code>EXPOSE_xxx</code>
0310:             * constants.
0311:             */
0312:            public void setExposureLevel(int exposureLevel) {
0313:                if (exposureLevel < EXPOSE_ALL
0314:                        || exposureLevel > EXPOSE_NOTHING) {
0315:                    throw new IllegalArgumentException(
0316:                            "Illegal exposure level " + exposureLevel);
0317:                }
0318:                this .exposureLevel = exposureLevel;
0319:            }
0320:
0321:            int getExposureLevel() {
0322:                return exposureLevel;
0323:            }
0324:
0325:            public void setExposeFields(boolean exposeFields) {
0326:                this .exposeFields = exposeFields;
0327:            }
0328:
0329:            public boolean isExposeFields() {
0330:                return exposeFields;
0331:            }
0332:
0333:            /**
0334:             * Sets whether methods shadow items in beans. When true (this is the
0335:             * default value), <code>${object.name}</code> will first try to locate
0336:             * a bean method or property with the specified name on the object, and
0337:             * only if it doesn't find it will it try to call
0338:             * <code>object.get(name)</code>, the so-called "generic get method" that
0339:             * is usually used to access items of a container (i.e. elements of a map).
0340:             * When set to false, the lookup order is reversed and generic get method
0341:             * is called first, and only if it returns null is method lookup attempted.
0342:             */
0343:            public synchronized void setMethodsShadowItems(
0344:                    boolean methodsShadowItems) {
0345:                this .methodsShadowItems = methodsShadowItems;
0346:            }
0347:
0348:            boolean isMethodsShadowItems() {
0349:                return methodsShadowItems;
0350:            }
0351:
0352:            /**
0353:             * Sets the default date type to use for date models that result from
0354:             * a plain <tt>java.util.Date</tt> instead of <tt>java.sql.Date</tt> or
0355:             * <tt>java.sql.Time</tt> or <tt>java.sql.Timestamp</tt>. Default value is 
0356:             * {@link TemplateDateModel#UNKNOWN}.
0357:             * @param defaultDateType the new default date type.
0358:             */
0359:            public synchronized void setDefaultDateType(int defaultDateType) {
0360:                this .defaultDateType = defaultDateType;
0361:            }
0362:
0363:            protected int getDefaultDateType() {
0364:                return defaultDateType;
0365:            }
0366:
0367:            /**
0368:             * Sets whether this wrapper caches model instances. Default is false.
0369:             * When set to true, calling {@link #wrap(Object)} multiple times for
0370:             * the same object will likely return the same model (although there is
0371:             * no guarantee as the cache items can be cleared anytime).
0372:             */
0373:            public void setUseCache(boolean useCache) {
0374:                modelCache.setUseCache(useCache);
0375:            }
0376:
0377:            /**
0378:             * Sets the null model. This model is returned from the
0379:             * {@link #wrap(Object)} method whenever the underlying object 
0380:             * reference is null. It defaults to null reference, which is dealt 
0381:             * with quite strictly on engine level, however you can substitute an 
0382:             * arbitrary (perhaps more lenient) model, such as 
0383:             * {@link freemarker.template.TemplateScalarModel#EMPTY_STRING}.
0384:             */
0385:            public void setNullModel(TemplateModel nullModel) {
0386:                this .nullModel = nullModel;
0387:            }
0388:
0389:            /**
0390:             * Returns the default instance of the wrapper. This instance is used
0391:             * when you construct various bean models without explicitly specifying
0392:             * a wrapper. It is also returned by 
0393:             * {@link freemarker.template.ObjectWrapper#BEANS_WRAPPER}
0394:             * and this is the sole instance that is used by the JSP adapter.
0395:             * You can modify the properties of the default instance (caching,
0396:             * exposure level, null model) to affect its operation. By default, the
0397:             * default instance is not caching, uses the <code>EXPOSE_SAFE</code>
0398:             * exposure level, and uses null reference as the null model.
0399:             */
0400:            public static final BeansWrapper getDefaultInstance() {
0401:                return INSTANCE;
0402:            }
0403:
0404:            /**
0405:             * Wraps the object with a template model that is most specific for the object's
0406:             * class. Specifically:
0407:             * <ul>
0408:             * <li>if the object is null, returns the {@link #setNullModel(TemplateModel) null model},</li>
0409:             * <li>if the object is a Number returns a {@link NumberModel} for it,</li>
0410:             * <li>if the object is a Date returns a {@link DateModel} for it,</li>
0411:             * <li>if the object is a Boolean returns 
0412:             * {@link freemarker.template.TemplateBooleanModel#TRUE} or 
0413:             * {@link freemarker.template.TemplateBooleanModel#FALSE}</li>
0414:             * <li>if the object is already a TemplateModel, returns it unchanged,</li>
0415:             * <li>if the object is an array, returns a {@link ArrayModel} for it
0416:             * <li>if the object is a Map, returns a {@link MapModel} for it
0417:             * <li>if the object is a Collection, returns a {@link CollectionModel} for it
0418:             * <li>if the object is an Iterator, returns a {@link IteratorModel} for it
0419:             * <li>if the object is an Enumeration, returns a {@link EnumerationModel} for it
0420:             * <li>if the object is a String, returns a {@link StringModel} for it
0421:             * <li>otherwise, returns a generic {@link BeanModel} for it.
0422:             * </ul>
0423:             */
0424:            public TemplateModel wrap(Object object)
0425:                    throws TemplateModelException {
0426:                if (object == null)
0427:                    return nullModel;
0428:                if (object instanceof  TemplateModel)
0429:                    return (TemplateModel) object;
0430:                if (object instanceof  TemplateModelAdapter)
0431:                    return ((TemplateModelAdapter) object).getTemplateModel();
0432:                if (object instanceof  Map)
0433:                    return modelCache.getInstance(object,
0434:                            simpleMapWrapper ? SimpleMapModel.FACTORY
0435:                                    : MapModel.FACTORY);
0436:                if (object instanceof  Collection)
0437:                    return modelCache.getInstance(object,
0438:                            CollectionModel.FACTORY);
0439:                if (object.getClass().isArray())
0440:                    return modelCache.getInstance(object, ArrayModel.FACTORY);
0441:                if (object instanceof  Number)
0442:                    return modelCache.getInstance(object, NumberModel.FACTORY);
0443:                if (object instanceof  Date)
0444:                    return modelCache.getInstance(object, DateModel.FACTORY);
0445:                if (object instanceof  Boolean)
0446:                    return ((Boolean) object).booleanValue() ? TRUE : FALSE;
0447:                if (object instanceof  ResourceBundle)
0448:                    return modelCache.getInstance(object,
0449:                            ResourceBundleModel.FACTORY);
0450:                if (object instanceof  Iterator)
0451:                    return new IteratorModel((Iterator) object, this );
0452:                if (object instanceof  Enumeration)
0453:                    return new EnumerationModel((Enumeration) object, this );
0454:                return modelCache.getInstance(object, StringModel.FACTORY);
0455:            }
0456:
0457:            protected TemplateModel getInstance(Object object,
0458:                    ModelFactory factory) {
0459:                return modelCache.getInstance(object, factory);
0460:            }
0461:
0462:            protected TemplateModel create(Object object, Object factory) {
0463:                return ((ModelFactory) factory).create(object, this );
0464:            }
0465:
0466:            /**
0467:             * Attempts to unwrap a model into underlying object. Generally, this
0468:             * method is the inverse of the {@link #wrap(Object)} method. In addition
0469:             * it will unwrap arbitrary {@link TemplateNumberModel} instances into
0470:             * a number, arbitrary {@link TemplateDateModel} instances into a date,
0471:             * {@link TemplateScalarModel} instances into a String, and
0472:             * {@link TemplateBooleanModel} instances into a Boolean.
0473:             * All other objects are returned unchanged.
0474:             */
0475:            public Object unwrap(TemplateModel model)
0476:                    throws TemplateModelException {
0477:                return unwrap(model, OBJECT_CLASS);
0478:            }
0479:
0480:            public Object unwrap(TemplateModel model, Class hint)
0481:                    throws TemplateModelException {
0482:                return unwrap(model, hint, null);
0483:            }
0484:
0485:            private Object unwrap(TemplateModel model, Class hint,
0486:                    Map recursionStops) throws TemplateModelException {
0487:                if (model == nullModel) {
0488:                    return null;
0489:                }
0490:
0491:                boolean isBoolean = Boolean.TYPE == hint;
0492:                boolean isChar = Character.TYPE == hint;
0493:
0494:                // This is for transparent interop with other wrappers (and ourselves)
0495:                // Passing the hint allows i.e. a Jython-aware method that declares a
0496:                // PyObject as its argument to receive a PyObject from a JythonModel
0497:                // passed as an argument to TemplateMethodModelEx etc.
0498:                if (model instanceof  AdapterTemplateModel) {
0499:                    Object adapted = ((AdapterTemplateModel) model)
0500:                            .getAdaptedObject(hint);
0501:                    if (hint.isInstance(adapted)) {
0502:                        return adapted;
0503:                    }
0504:                    // Attempt numeric conversion 
0505:                    if (adapted instanceof  Number
0506:                            && ((hint.isPrimitive() && !isChar && !isBoolean) || NUMBER_CLASS
0507:                                    .isAssignableFrom(hint))) {
0508:                        Number number = convertUnwrappedNumber(hint,
0509:                                (Number) adapted);
0510:                        if (number != null) {
0511:                            return number;
0512:                        }
0513:                    }
0514:                }
0515:
0516:                if (model instanceof  WrapperTemplateModel) {
0517:                    Object wrapped = ((WrapperTemplateModel) model)
0518:                            .getWrappedObject();
0519:                    if (hint.isInstance(wrapped)) {
0520:                        return wrapped;
0521:                    }
0522:                    // Attempt numeric conversion 
0523:                    if (wrapped instanceof  Number
0524:                            && ((hint.isPrimitive() && !isChar && !isBoolean) || NUMBER_CLASS
0525:                                    .isAssignableFrom(hint))) {
0526:                        Number number = convertUnwrappedNumber(hint,
0527:                                (Number) wrapped);
0528:                        if (number != null) {
0529:                            return number;
0530:                        }
0531:                    }
0532:                }
0533:
0534:                // Translation of generic template models to POJOs. First give priority
0535:                // to various model interfaces based on the hint class. This helps us
0536:                // select the appropriate interface in multi-interface models when we
0537:                // know what is expected as the return type.
0538:
0539:                if (STRING_CLASS == hint) {
0540:                    if (model instanceof  TemplateScalarModel) {
0541:                        return ((TemplateScalarModel) model).getAsString();
0542:                    }
0543:                    // String is final, so no other conversion will work
0544:                    throw canNotConvert(model, hint);
0545:                }
0546:
0547:                // Primitive numeric types & Number.class and its subclasses
0548:                if ((hint.isPrimitive() && !isChar && !isBoolean)
0549:                        || NUMBER_CLASS.isAssignableFrom(hint)) {
0550:                    if (model instanceof  TemplateNumberModel) {
0551:                        Number number = convertUnwrappedNumber(hint,
0552:                                ((TemplateNumberModel) model).getAsNumber());
0553:                        if (number != null) {
0554:                            return number;
0555:                        }
0556:                    }
0557:                }
0558:
0559:                if (isBoolean || BOOLEAN_CLASS == hint) {
0560:                    if (model instanceof  TemplateBooleanModel) {
0561:                        return ((TemplateBooleanModel) model).getAsBoolean() ? Boolean.TRUE
0562:                                : Boolean.FALSE;
0563:                    }
0564:                    // Boolean is final, no other conversion will work
0565:                    throw canNotConvert(model, hint);
0566:                }
0567:
0568:                if (MAP_CLASS == hint) {
0569:                    if (model instanceof  TemplateHashModel) {
0570:                        return new HashAdapter((TemplateHashModel) model, this );
0571:                    }
0572:                }
0573:
0574:                if (LIST_CLASS == hint) {
0575:                    if (model instanceof  TemplateSequenceModel) {
0576:                        return new SequenceAdapter(
0577:                                (TemplateSequenceModel) model, this );
0578:                    }
0579:                }
0580:
0581:                if (SET_CLASS == hint) {
0582:                    if (model instanceof  TemplateCollectionModel) {
0583:                        return new SetAdapter((TemplateCollectionModel) model,
0584:                                this );
0585:                    }
0586:                }
0587:
0588:                if (COLLECTION_CLASS == hint || ITERABLE_CLASS == hint) {
0589:                    if (model instanceof  TemplateCollectionModel) {
0590:                        return new CollectionAdapter(
0591:                                (TemplateCollectionModel) model, this );
0592:                    }
0593:                    if (model instanceof  TemplateSequenceModel) {
0594:                        return new SequenceAdapter(
0595:                                (TemplateSequenceModel) model, this );
0596:                    }
0597:                }
0598:
0599:                // TemplateSequenceModels can be converted to arrays
0600:                if (hint.isArray()) {
0601:                    if (model instanceof  TemplateSequenceModel) {
0602:                        if (recursionStops != null) {
0603:                            Object retval = recursionStops.get(model);
0604:                            if (retval != null) {
0605:                                return retval;
0606:                            }
0607:                        } else {
0608:                            recursionStops = new IdentityHashMap();
0609:                        }
0610:                        TemplateSequenceModel seq = (TemplateSequenceModel) model;
0611:                        Class componentType = hint.getComponentType();
0612:                        Object array = Array.newInstance(componentType, seq
0613:                                .size());
0614:                        recursionStops.put(model, array);
0615:                        try {
0616:                            int size = seq.size();
0617:                            for (int i = 0; i < size; i++) {
0618:                                Array.set(array, i, unwrap(model,
0619:                                        componentType, recursionStops));
0620:                            }
0621:                        } finally {
0622:                            recursionStops.remove(model);
0623:                        }
0624:                        return array;
0625:                    }
0626:                    // array classes are final, no other conversion will work
0627:                    throw canNotConvert(model, hint);
0628:                }
0629:
0630:                // Allow one-char strings to be coerced to characters
0631:                if (isChar || hint == CHARACTER_CLASS) {
0632:                    if (model instanceof  TemplateScalarModel) {
0633:                        String s = ((TemplateScalarModel) model).getAsString();
0634:                        if (s.length() == 1) {
0635:                            return new Character(s.charAt(0));
0636:                        }
0637:                    }
0638:                    // Character is final, no other conversion will work
0639:                    throw canNotConvert(model, hint);
0640:                }
0641:
0642:                if (DATE_CLASS.isAssignableFrom(hint)) {
0643:                    if (model instanceof  TemplateDateModel) {
0644:                        Date date = ((TemplateDateModel) model).getAsDate();
0645:                        if (hint.isInstance(date)) {
0646:                            return date;
0647:                        }
0648:                    }
0649:                }
0650:
0651:                // Translation of generic template models to POJOs. Since hint was of
0652:                // no help initially, now use an admittedly arbitrary order of 
0653:                // interfaces. Note we still test for isInstance and isAssignableFrom
0654:                // to guarantee we return a compatible value. 
0655:                if (model instanceof  TemplateNumberModel) {
0656:                    Number number = ((TemplateNumberModel) model).getAsNumber();
0657:                    if (hint.isInstance(number)) {
0658:                        return number;
0659:                    }
0660:                }
0661:                if (model instanceof  TemplateDateModel) {
0662:                    Date date = ((TemplateDateModel) model).getAsDate();
0663:                    if (hint.isInstance(date)) {
0664:                        return date;
0665:                    }
0666:                }
0667:                if (model instanceof  TemplateScalarModel
0668:                        && hint.isAssignableFrom(STRING_CLASS)) {
0669:                    return ((TemplateScalarModel) model).getAsString();
0670:                }
0671:                if (model instanceof  TemplateBooleanModel
0672:                        && hint.isAssignableFrom(BOOLEAN_CLASS)) {
0673:                    return ((TemplateBooleanModel) model).getAsBoolean() ? Boolean.TRUE
0674:                            : Boolean.FALSE;
0675:                }
0676:                if (model instanceof  TemplateHashModel
0677:                        && hint.isAssignableFrom(HASHADAPTER_CLASS)) {
0678:                    return new HashAdapter((TemplateHashModel) model, this );
0679:                }
0680:                if (model instanceof  TemplateSequenceModel
0681:                        && hint.isAssignableFrom(SEQUENCEADAPTER_CLASS)) {
0682:                    return new SequenceAdapter((TemplateSequenceModel) model,
0683:                            this );
0684:                }
0685:                if (model instanceof  TemplateCollectionModel
0686:                        && hint.isAssignableFrom(SETADAPTER_CLASS)) {
0687:                    return new SetAdapter((TemplateCollectionModel) model, this );
0688:                }
0689:
0690:                // Last ditch effort - is maybe the model itself instance of the 
0691:                // required type?
0692:                if (hint.isInstance(model)) {
0693:                    return model;
0694:                }
0695:
0696:                throw canNotConvert(model, hint);
0697:            }
0698:
0699:            private static Number convertUnwrappedNumber(Class hint,
0700:                    Number number) {
0701:                if (hint == Integer.TYPE || hint == Integer.class) {
0702:                    return number instanceof  Integer ? (Integer) number
0703:                            : new Integer(number.intValue());
0704:                }
0705:                if (hint == Long.TYPE || hint == Long.class) {
0706:                    return number instanceof  Long ? (Long) number : new Long(
0707:                            number.longValue());
0708:                }
0709:                if (hint == Float.TYPE || hint == Float.class) {
0710:                    return number instanceof  Float ? (Float) number
0711:                            : new Float(number.longValue());
0712:                }
0713:                if (hint == Double.TYPE || hint == Double.class) {
0714:                    return number instanceof  Double ? (Double) number
0715:                            : new Double(number.longValue());
0716:                }
0717:                if (hint == Byte.TYPE || hint == Byte.class) {
0718:                    return number instanceof  Byte ? (Byte) number : new Byte(
0719:                            number.byteValue());
0720:                }
0721:                if (hint == Short.TYPE || hint == Short.class) {
0722:                    return number instanceof  Short ? (Short) number
0723:                            : new Short(number.shortValue());
0724:                }
0725:                if (hint == BigInteger.class) {
0726:                    return number instanceof  BigInteger ? number
0727:                            : new BigInteger(number.toString());
0728:                }
0729:                if (hint == BigDecimal.class) {
0730:                    if (number instanceof  BigDecimal) {
0731:                        return number;
0732:                    }
0733:                    if (number instanceof  BigInteger) {
0734:                        return new BigDecimal((BigInteger) number);
0735:                    }
0736:                    if (number instanceof  Long) {
0737:                        // Because we can't represent long accurately as a 
0738:                        // double
0739:                        return new BigDecimal(number.toString());
0740:                    }
0741:                    return new BigDecimal(number.doubleValue());
0742:                }
0743:                // Handle nonstandard Number subclasses as well as directly 
0744:                // java.lang.Number too
0745:                if (hint.isInstance(number)) {
0746:                    return number;
0747:                }
0748:                return null;
0749:            }
0750:
0751:            private static TemplateModelException canNotConvert(
0752:                    TemplateModel model, Class hint) {
0753:                if (model == null) {
0754:                    return new TemplateModelException(
0755:                            "Could not convert null to " + hint.getName());
0756:                }
0757:                return new TemplateModelException(
0758:                        "Could not convert an instance of "
0759:                                + model.getClass().getName() + " with value ["
0760:                                + model.toString() + "] to " + hint.getName());
0761:            }
0762:
0763:            /**
0764:             * Auxiliary method that unwraps arguments for a method or constructor call.
0765:             * @param arguments the argument list of template models
0766:             * @param argTypes the preferred types of the arguments
0767:             * @return Object[] the unwrapped arguments. null if the passed list was
0768:             * null.
0769:             * @throws TemplateModelException if unwrapping any argument throws one
0770:             */
0771:            Object[] unwrapArguments(List arguments, Class[] argTypes)
0772:                    throws TemplateModelException {
0773:                Object[] args = null;
0774:                if (arguments != null) {
0775:                    int size = arguments.size();
0776:                    args = new Object[size];
0777:                    Iterator it = arguments.iterator();
0778:                    for (int i = 0; it.hasNext(); ++i) {
0779:                        args[i] = unwrap((TemplateModel) it.next(), argTypes[i]);
0780:                    }
0781:                }
0782:                return args;
0783:            }
0784:
0785:            Object[] unwrapArguments(List arguments)
0786:                    throws TemplateModelException {
0787:                Object[] args = null;
0788:                if (arguments != null) {
0789:                    int size = arguments.size();
0790:                    args = new Object[size];
0791:                    Iterator it = arguments.iterator();
0792:                    int i = 0;
0793:                    while (it.hasNext()) {
0794:                        args[i++] = unwrap((TemplateModel) it.next());
0795:                    }
0796:                }
0797:                return args;
0798:            }
0799:
0800:            /**
0801:             * Invokes the specified method, wrapping the return value. The specialty
0802:             * of this method is that if the return value is null, and the return type
0803:             * of the invoked method is void, {@link TemplateModel#NOTHING} is returned.
0804:             * @param object the object to invoke the method on
0805:             * @param method the method to invoke 
0806:             * @param args the arguments to the method
0807:             * @return the wrapped return value of the method.
0808:             * @throws InvocationTargetException if the invoked method threw an exception
0809:             * @throws IllegalAccessException if the method can't be invoked due to an
0810:             * access restriction. 
0811:             * @throws TemplateModelException if the return value couldn't be wrapped
0812:             * (this can happen if the wrapper has an outer identity or is subclassed,
0813:             * and the outer identity or the subclass throws an exception. Plain
0814:             * BeansWrapper never throws TemplateModelException).
0815:             */
0816:            TemplateModel invokeMethod(Object object, Method method,
0817:                    Object[] args) throws InvocationTargetException,
0818:                    IllegalAccessException, TemplateModelException {
0819:                Object retval = method.invoke(object, args);
0820:                return method.getReturnType() == Void.TYPE ? TemplateModel.NOTHING
0821:                        : getOuterIdentity().wrap(retval);
0822:            }
0823:
0824:            /**
0825:             * Returns a hash model that represents the so-called class static models.
0826:             * Every class static model is itself a hash through which you can call
0827:             * static methods on the specified class. To obtain a static model for a
0828:             * class, get the element of this hash with the fully qualified class name.
0829:             * For example, if you place this hash model inside the root data model
0830:             * under name "statics", you can use i.e. <code>statics["java.lang.
0831:             * System"]. currentTimeMillis()</code> to call the {@link 
0832:             * java.lang.System#currentTimeMillis()} method.
0833:             * @return a hash model whose keys are fully qualified class names, and
0834:             * that returns hash models whose elements are the static models of the
0835:             * classes.
0836:             */
0837:            public TemplateHashModel getStaticModels() {
0838:                return staticModels;
0839:            }
0840:
0841:            /**
0842:             * Returns a hash model that represents the so-called class enum models.
0843:             * Every class' enum model is itself a hash through which you can access
0844:             * enum value declared by the specified class, assuming that class is an
0845:             * enumeration. To obtain an enum model for a class, get the element of this
0846:             * hash with the fully qualified class name. For example, if you place this 
0847:             * hash model inside the root data model under name "enums", you can use 
0848:             * i.e. <code>statics["java.math.RoundingMode"].UP</code> to access the 
0849:             * {@link java.math.RoundingMode#UP} value.
0850:             * @return a hash model whose keys are fully qualified class names, and
0851:             * that returns hash models whose elements are the enum models of the
0852:             * classes.
0853:             * @throws UnsupportedOperationException if this method is invoked on a 
0854:             * pre-1.5 JRE, as Java enums aren't supported there.
0855:             */
0856:            public TemplateHashModel getEnumModels() {
0857:                if (enumModels == null) {
0858:                    throw new UnsupportedOperationException(
0859:                            "Enums not supported on pre-1.5 JRE");
0860:                }
0861:                return enumModels;
0862:            }
0863:
0864:            public Object newInstance(Class clazz, List arguments)
0865:                    throws TemplateModelException {
0866:                try {
0867:                    introspectClass(clazz);
0868:                    Map classInfo = (Map) classCache.get(clazz);
0869:                    Object ctors = classInfo.get(CONSTRUCTORS);
0870:                    if (ctors == null) {
0871:                        throw new TemplateModelException("Class "
0872:                                + clazz.getName()
0873:                                + " has no public constructors.");
0874:                    }
0875:                    Constructor ctor = null;
0876:                    Object[] objargs;
0877:                    if (ctors instanceof  Constructor) {
0878:                        ctor = (Constructor) ctors;
0879:                        objargs = unwrapArguments(arguments, getArgTypes(
0880:                                classInfo, ctor));
0881:                    } else if (ctors instanceof  MethodMap) {
0882:                        MethodMap methodMap = (MethodMap) ctors;
0883:                        objargs = unwrapArguments(arguments, methodMap
0884:                                .getUnwrapTypes(arguments));
0885:                        ctor = (Constructor) methodMap.getMostSpecific(objargs);
0886:                    } else {
0887:                        // Cannot happen
0888:                        throw new Error();
0889:                    }
0890:                    if (objargs != null) {
0891:                        coerceBigDecimals(ctor, objargs);
0892:                    }
0893:                    return ctor.newInstance(objargs);
0894:                } catch (TemplateModelException e) {
0895:                    throw e;
0896:                } catch (Exception e) {
0897:                    throw new TemplateModelException(
0898:                            "Could not create instance of class "
0899:                                    + clazz.getName(), e);
0900:                }
0901:            }
0902:
0903:            void introspectClass(Class clazz) {
0904:                synchronized (classCache) {
0905:                    if (!classCache.containsKey(clazz)) {
0906:                        introspectClassInternal(clazz);
0907:                    }
0908:                }
0909:            }
0910:
0911:            private void introspectClassInternal(Class clazz) {
0912:                String className = clazz.getName();
0913:                if (cachedClassNames.contains(className)) {
0914:                    if (logger.isInfoEnabled()) {
0915:                        logger.info("Detected a reloaded class [" + className
0916:                                + "]. Clearing BeansWrapper caches.");
0917:                    }
0918:                    // Class reload detected, throw away caches
0919:                    classCache.clear();
0920:                    cachedClassNames = new HashSet();
0921:                    synchronized (this ) {
0922:                        modelCache.clearCache();
0923:                    }
0924:                    staticModels.clearCache();
0925:                    if (enumModels != null) {
0926:                        enumModels.clearCache();
0927:                    }
0928:                }
0929:                classCache.put(clazz, populateClassMap(clazz));
0930:                cachedClassNames.add(className);
0931:            }
0932:
0933:            Map getClassKeyMap(Class clazz) {
0934:                Map map;
0935:                synchronized (classCache) {
0936:                    map = (Map) classCache.get(clazz);
0937:                    if (map == null) {
0938:                        introspectClassInternal(clazz);
0939:                        map = (Map) classCache.get(clazz);
0940:                    }
0941:                }
0942:                return map;
0943:            }
0944:
0945:            /**
0946:             * Returns the number of introspected methods/properties that should
0947:             * be available via the TemplateHashModel interface. Affected by the
0948:             * {@link #setMethodsShadowItems(boolean)} and {@link
0949:             * #setExposureLevel(int)} settings.
0950:             */
0951:            int keyCount(Class clazz) {
0952:                Map map = getClassKeyMap(clazz);
0953:                int count = map.size();
0954:                if (map.containsKey(CONSTRUCTORS))
0955:                    count--;
0956:                if (map.containsKey(GENERIC_GET_KEY))
0957:                    count--;
0958:                if (map.containsKey(ARGTYPES))
0959:                    count--;
0960:                return count;
0961:            }
0962:
0963:            /**
0964:             * Returns the Set of names of introspected methods/properties that
0965:             * should be available via the TemplateHashModel interface. Affected
0966:             * by the {@link #setMethodsShadowItems(boolean)} and {@link
0967:             * #setExposureLevel(int)} settings.
0968:             */
0969:            Set keySet(Class clazz) {
0970:                Set set = new HashSet(getClassKeyMap(clazz).keySet());
0971:                set.remove(CONSTRUCTORS);
0972:                set.remove(GENERIC_GET_KEY);
0973:                set.remove(ARGTYPES);
0974:                return set;
0975:            }
0976:
0977:            /**
0978:             * Populates a map with property and method descriptors for a specified
0979:             * class. If any property or method descriptors specifies a read method
0980:             * that is not accessible, replaces it with appropriate accessible method
0981:             * from a superclass or interface.
0982:             */
0983:            private Map populateClassMap(Class clazz) {
0984:                // Populate first from bean info
0985:                Map map = populateClassMapWithBeanInfo(clazz);
0986:                // Next add constructors
0987:                try {
0988:                    Constructor[] ctors = clazz.getConstructors();
0989:                    if (ctors.length == 1) {
0990:                        Constructor ctor = ctors[0];
0991:                        map.put(CONSTRUCTORS, ctor);
0992:                        getArgTypes(map).put(ctor, ctor.getParameterTypes());
0993:                    } else if (ctors.length > 1) {
0994:                        MethodMap ctorMap = new MethodMap("<init>");
0995:                        for (int i = 0; i < ctors.length; i++) {
0996:                            ctorMap.addConstructor(ctors[i]);
0997:                        }
0998:                        map.put(CONSTRUCTORS, ctorMap);
0999:                    }
1000:                } catch (SecurityException e) {
1001:                    logger.warn("Canont discover constructors for class "
1002:                            + clazz.getName(), e);
1003:                }
1004:                switch (map.size()) {
1005:                case 0: {
1006:                    map = Collections12.EMPTY_MAP;
1007:                    break;
1008:                }
1009:                case 1: {
1010:                    Map.Entry e = (Map.Entry) map.entrySet().iterator().next();
1011:                    map = Collections12.singletonMap(e.getKey(), e.getValue());
1012:                    break;
1013:                }
1014:                }
1015:                return map;
1016:            }
1017:
1018:            private Map populateClassMapWithBeanInfo(Class clazz) {
1019:                Map classMap = new HashMap();
1020:                if (exposeFields) {
1021:                    Field[] fields = clazz.getFields();
1022:                    for (int i = 0; i < fields.length; i++) {
1023:                        Field field = fields[i];
1024:                        if ((field.getModifiers() & Modifier.STATIC) == 0) {
1025:                            classMap.put(field.getName(), field);
1026:                        }
1027:                    }
1028:                }
1029:                Map accessibleMethods = discoverAccessibleMethods(clazz);
1030:                Method genericGet = (Method) accessibleMethods
1031:                        .get(MethodSignature.GET_STRING_SIGNATURE);
1032:                if (genericGet == null) {
1033:                    genericGet = (Method) accessibleMethods
1034:                            .get(MethodSignature.GET_OBJECT_SIGNATURE);
1035:                }
1036:                if (genericGet != null) {
1037:                    classMap.put(GENERIC_GET_KEY, genericGet);
1038:                }
1039:                if (exposureLevel == EXPOSE_NOTHING) {
1040:                    return classMap;
1041:                }
1042:
1043:                try {
1044:                    BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
1045:                    PropertyDescriptor[] pda = beanInfo
1046:                            .getPropertyDescriptors();
1047:                    MethodDescriptor[] mda = beanInfo.getMethodDescriptors();
1048:
1049:                    for (int i = pda.length - 1; i >= 0; --i) {
1050:                        PropertyDescriptor pd = pda[i];
1051:                        if (pd instanceof  IndexedPropertyDescriptor) {
1052:                            IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd;
1053:                            Method readMethod = ipd.getIndexedReadMethod();
1054:                            Method publicReadMethod = getAccessibleMethod(
1055:                                    readMethod, accessibleMethods);
1056:                            if (publicReadMethod != null
1057:                                    && isSafeMethod(publicReadMethod)) {
1058:                                try {
1059:                                    if (readMethod != publicReadMethod) {
1060:                                        ipd = new IndexedPropertyDescriptor(ipd
1061:                                                .getName(),
1062:                                                ipd.getReadMethod(), ipd
1063:                                                        .getWriteMethod(),
1064:                                                publicReadMethod,
1065:                                                ipd.getIndexedWriteMethod());
1066:                                    }
1067:                                    classMap.put(ipd.getName(), ipd);
1068:                                    getArgTypes(classMap).put(
1069:                                            publicReadMethod,
1070:                                            publicReadMethod
1071:                                                    .getParameterTypes());
1072:                                } catch (IntrospectionException e) {
1073:                                    logger
1074:                                            .warn(
1075:                                                    "Couldn't properly perform introspection",
1076:                                                    e);
1077:                                }
1078:                            }
1079:                        } else {
1080:                            Method readMethod = pd.getReadMethod();
1081:                            Method publicReadMethod = getAccessibleMethod(
1082:                                    readMethod, accessibleMethods);
1083:                            if (publicReadMethod != null
1084:                                    && isSafeMethod(publicReadMethod)) {
1085:                                try {
1086:                                    if (readMethod != publicReadMethod) {
1087:                                        pd = new PropertyDescriptor(pd
1088:                                                .getName(), publicReadMethod,
1089:                                                pd.getWriteMethod());
1090:                                        pd.setReadMethod(publicReadMethod);
1091:                                    }
1092:                                    classMap.put(pd.getName(), pd);
1093:                                } catch (IntrospectionException e) {
1094:                                    logger
1095:                                            .warn(
1096:                                                    "Couldn't properly perform introspection",
1097:                                                    e);
1098:                                }
1099:                            }
1100:                        }
1101:                    }
1102:                    if (exposureLevel < EXPOSE_PROPERTIES_ONLY) {
1103:                        for (int i = mda.length - 1; i >= 0; --i) {
1104:                            MethodDescriptor md = mda[i];
1105:                            Method method = md.getMethod();
1106:                            Method publicMethod = getAccessibleMethod(method,
1107:                                    accessibleMethods);
1108:                            if (publicMethod != null
1109:                                    && isSafeMethod(publicMethod)) {
1110:                                String name = md.getName();
1111:                                Object previous = classMap.get(name);
1112:                                if (previous instanceof  Method) {
1113:                                    // Overloaded method - replace method with a method map
1114:                                    MethodMap methodMap = new MethodMap(name);
1115:                                    methodMap.addMethod((Method) previous);
1116:                                    methodMap.addMethod(publicMethod);
1117:                                    classMap.put(name, methodMap);
1118:                                    // remove parameter type information
1119:                                    getArgTypes(classMap).remove(previous);
1120:                                } else if (previous instanceof  MethodMap) {
1121:                                    // Already overloaded method - add new overload
1122:                                    ((MethodMap) previous)
1123:                                            .addMethod(publicMethod);
1124:                                } else {
1125:                                    // Simple method (this far)
1126:                                    classMap.put(name, publicMethod);
1127:                                    getArgTypes(classMap).put(publicMethod,
1128:                                            publicMethod.getParameterTypes());
1129:                                }
1130:                            }
1131:                        }
1132:                    }
1133:                    return classMap;
1134:                } catch (IntrospectionException e) {
1135:                    logger.warn("Couldn't properly perform introspection", e);
1136:                    return new HashMap();
1137:                }
1138:            }
1139:
1140:            private static Map getArgTypes(Map classMap) {
1141:                Map argTypes = (Map) classMap.get(ARGTYPES);
1142:                if (argTypes == null) {
1143:                    argTypes = new HashMap();
1144:                    classMap.put(ARGTYPES, argTypes);
1145:                }
1146:                return argTypes;
1147:            }
1148:
1149:            static Class[] getArgTypes(Map classMap,
1150:                    AccessibleObject methodOrCtor) {
1151:                return (Class[]) ((Map) classMap.get(ARGTYPES))
1152:                        .get(methodOrCtor);
1153:            }
1154:
1155:            private static Method getAccessibleMethod(Method m, Map accessibles) {
1156:                return m == null ? null : (Method) accessibles
1157:                        .get(new MethodSignature(m));
1158:            }
1159:
1160:            boolean isSafeMethod(Method method) {
1161:                return exposureLevel < EXPOSE_SAFE
1162:                        || !UNSAFE_METHODS.contains(method);
1163:            }
1164:
1165:            /**
1166:             * Retrieves mapping of methods to accessible methods for a class.
1167:             * In case the class is not public, retrieves methods with same 
1168:             * signature as its public methods from public superclasses and 
1169:             * interfaces (if they exist). Basically upcasts every method to the 
1170:             * nearest accessible method.
1171:             */
1172:            private static Map discoverAccessibleMethods(Class clazz) {
1173:                Map map = new HashMap();
1174:                discoverAccessibleMethods(clazz, map);
1175:                return map;
1176:            }
1177:
1178:            private static void discoverAccessibleMethods(Class clazz, Map map) {
1179:                if (Modifier.isPublic(clazz.getModifiers())) {
1180:                    try {
1181:                        Method[] methods = clazz.getMethods();
1182:                        for (int i = 0; i < methods.length; i++) {
1183:                            Method method = methods[i];
1184:                            MethodSignature sig = new MethodSignature(method);
1185:                            map.put(sig, method);
1186:                        }
1187:                        return;
1188:                    } catch (SecurityException e) {
1189:                        logger
1190:                                .warn(
1191:                                        "Could not discover accessible methods of class "
1192:                                                + clazz.getName()
1193:                                                + ", attemping superclasses/interfaces.",
1194:                                        e);
1195:                        // Fall through and attempt to discover superclass/interface 
1196:                        // methods
1197:                    }
1198:                }
1199:
1200:                Class[] interfaces = clazz.getInterfaces();
1201:                for (int i = 0; i < interfaces.length; i++) {
1202:                    discoverAccessibleMethods(interfaces[i], map);
1203:                }
1204:                Class super class = clazz.getSuperclass();
1205:                if (super class != null) {
1206:                    discoverAccessibleMethods(super class, map);
1207:                }
1208:            }
1209:
1210:            private static final class MethodSignature {
1211:                private static final MethodSignature GET_STRING_SIGNATURE = new MethodSignature(
1212:                        "get", new Class[] { STRING_CLASS });
1213:                private static final MethodSignature GET_OBJECT_SIGNATURE = new MethodSignature(
1214:                        "get", new Class[] { OBJECT_CLASS });
1215:
1216:                private final String name;
1217:                private final Class[] args;
1218:
1219:                private MethodSignature(String name, Class[] args) {
1220:                    this .name = name;
1221:                    this .args = args;
1222:                }
1223:
1224:                MethodSignature(Method method) {
1225:                    this (method.getName(), method.getParameterTypes());
1226:                }
1227:
1228:                public boolean equals(Object o) {
1229:                    if (o instanceof  MethodSignature) {
1230:                        MethodSignature ms = (MethodSignature) o;
1231:                        return ms.name.equals(name)
1232:                                && Arrays.equals(args, ms.args);
1233:                    }
1234:                    return false;
1235:                }
1236:
1237:                public int hashCode() {
1238:                    return name.hashCode() ^ args.length;
1239:                }
1240:            }
1241:
1242:            private static final Set createUnsafeMethodsSet() {
1243:                Properties props = new Properties();
1244:                InputStream in = BeansWrapper.class
1245:                        .getResourceAsStream("unsafeMethods.txt");
1246:                if (in != null) {
1247:                    String methodSpec = null;
1248:                    try {
1249:                        try {
1250:                            props.load(in);
1251:                        } finally {
1252:                            in.close();
1253:                        }
1254:                        Set set = new HashSet(props.size() * 4 / 3, .75f);
1255:                        Map primClasses = createPrimitiveClassesMap();
1256:                        for (Iterator iterator = props.keySet().iterator(); iterator
1257:                                .hasNext();) {
1258:                            methodSpec = (String) iterator.next();
1259:                            try {
1260:                                set
1261:                                        .add(parseMethodSpec(methodSpec,
1262:                                                primClasses));
1263:                            } catch (ClassNotFoundException e) {
1264:                                if (DEVELOPMENT) {
1265:                                    throw e;
1266:                                }
1267:                            } catch (NoSuchMethodException e) {
1268:                                if (DEVELOPMENT) {
1269:                                    throw e;
1270:                                }
1271:                            }
1272:                        }
1273:                        return set;
1274:                    } catch (Exception e) {
1275:                        throw new RuntimeException(
1276:                                "Could not load unsafe method " + methodSpec
1277:                                        + " " + e.getClass().getName() + " "
1278:                                        + e.getMessage());
1279:                    }
1280:                }
1281:                return Collections.EMPTY_SET;
1282:            }
1283:
1284:            private static Method parseMethodSpec(String methodSpec,
1285:                    Map primClasses) throws ClassNotFoundException,
1286:                    NoSuchMethodException {
1287:                int brace = methodSpec.indexOf('(');
1288:                int dot = methodSpec.lastIndexOf('.', brace);
1289:                Class clazz = ClassUtil.forName(methodSpec.substring(0, dot));
1290:                String methodName = methodSpec.substring(dot + 1, brace);
1291:                String argSpec = methodSpec.substring(brace + 1, methodSpec
1292:                        .length() - 1);
1293:                StringTokenizer tok = new StringTokenizer(argSpec, ",");
1294:                int argcount = tok.countTokens();
1295:                Class[] argTypes = new Class[argcount];
1296:                for (int i = 0; i < argcount; i++) {
1297:                    String argClassName = tok.nextToken();
1298:                    argTypes[i] = (Class) primClasses.get(argClassName);
1299:                    if (argTypes[i] == null) {
1300:                        argTypes[i] = ClassUtil.forName(argClassName);
1301:                    }
1302:                }
1303:                return clazz.getMethod(methodName, argTypes);
1304:            }
1305:
1306:            private static Map createPrimitiveClassesMap() {
1307:                Map map = new HashMap();
1308:                map.put("boolean", Boolean.TYPE);
1309:                map.put("byte", Byte.TYPE);
1310:                map.put("char", Character.TYPE);
1311:                map.put("short", Short.TYPE);
1312:                map.put("int", Integer.TYPE);
1313:                map.put("long", Long.TYPE);
1314:                map.put("float", Float.TYPE);
1315:                map.put("double", Double.TYPE);
1316:                return map;
1317:            }
1318:
1319:            /**
1320:             * Converts any {@link BigDecimal}s in the passed array to the type of
1321:             * the corresponding formal argument of the method.
1322:             */
1323:            public static void coerceBigDecimals(AccessibleObject callable,
1324:                    Object[] args) {
1325:                Class[] formalTypes = null;
1326:                for (int i = 0, l = args.length; i < l; ++i) {
1327:                    Object arg = args[i];
1328:                    if (arg instanceof  BigDecimal) {
1329:                        BigDecimal bd = (BigDecimal) arg;
1330:                        if (formalTypes == null) {
1331:                            if (callable instanceof  Method) {
1332:                                formalTypes = ((Method) callable)
1333:                                        .getParameterTypes();
1334:                            } else if (callable instanceof  Constructor) {
1335:                                formalTypes = ((Constructor) callable)
1336:                                        .getParameterTypes();
1337:                            } else {
1338:                                // Cannot happen
1339:                                throw new Error();
1340:                            }
1341:                            if (formalTypes.length != l) {
1342:                                // This will die anyway due to incorrect number of
1343:                                // arguments, so there's no point in checking
1344:                                return;
1345:                            }
1346:                        }
1347:                        Class formalType = formalTypes[i];
1348:                        // int is expected in most situations, so we check it first
1349:                        if (formalType == Integer.TYPE
1350:                                || formalType == Integer.class) {
1351:                            args[i] = new Integer(bd.intValue());
1352:                        } else if (formalType == Double.TYPE
1353:                                || formalType == Double.class) {
1354:                            args[i] = new Double(bd.doubleValue());
1355:                        } else if (formalType == Long.TYPE
1356:                                || formalType == Long.class) {
1357:                            args[i] = new Long(bd.longValue());
1358:                        } else if (formalType == Float.TYPE
1359:                                || formalType == Float.class) {
1360:                            args[i] = new Float(bd.floatValue());
1361:                        } else if (formalType == Short.TYPE
1362:                                || formalType == Short.class) {
1363:                            args[i] = new Short(bd.shortValue());
1364:                        } else if (formalType == Byte.TYPE
1365:                                || formalType == Byte.class) {
1366:                            args[i] = new Byte(bd.byteValue());
1367:                        } else if (BIGINTEGER_CLASS
1368:                                .isAssignableFrom(formalType)) {
1369:                            args[i] = bd.toBigInteger();
1370:                        }
1371:                    }
1372:                }
1373:            }
1374:
1375:            private static ClassBasedModelFactory createEnumModels(
1376:                    BeansWrapper wrapper) {
1377:                if (ENUMS_MODEL_CTOR != null) {
1378:                    try {
1379:                        return (ClassBasedModelFactory) ENUMS_MODEL_CTOR
1380:                                .newInstance(new Object[] { wrapper });
1381:                    } catch (Exception e) {
1382:                        throw new UndeclaredThrowableException(e);
1383:                    }
1384:                } else {
1385:                    return null;
1386:                }
1387:            }
1388:
1389:            private static Constructor enumsModelCtor() {
1390:                try {
1391:                    // Check if Enums are available on this platform
1392:                    Class.forName("java.lang.Enum");
1393:                    // If they are, return the appropriate constructor for enum models
1394:                    return Class.forName("freemarker.ext.beans.EnumModels")
1395:                            .getDeclaredConstructor(
1396:                                    new Class[] { BeansWrapper.class });
1397:                } catch (Exception e) {
1398:                    // Otherwise, return null
1399:                    return null;
1400:                }
1401:            }
1402:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.