001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package javax.el;
019:
020: import java.beans.BeanInfo;
021: import java.beans.FeatureDescriptor;
022: import java.beans.IntrospectionException;
023: import java.beans.Introspector;
024: import java.beans.PropertyDescriptor;
025: import java.lang.reflect.InvocationTargetException;
026: import java.lang.reflect.Method;
027: import java.lang.reflect.Modifier;
028: import java.util.Arrays;
029: import java.util.HashMap;
030: import java.util.Iterator;
031: import java.util.Map;
032: import java.util.WeakHashMap;
033: import java.util.concurrent.ConcurrentHashMap;
034:
035: public class BeanELResolver extends ELResolver {
036:
037: private final boolean readOnly;
038:
039: private final ConcurrentCache<String, BeanProperties> cache = new ConcurrentCache<String, BeanProperties>(
040: 1000);
041:
042: public BeanELResolver() {
043: this .readOnly = false;
044: }
045:
046: public BeanELResolver(boolean readOnly) {
047: this .readOnly = readOnly;
048: }
049:
050: public Object getValue(ELContext context, Object base,
051: Object property) throws NullPointerException,
052: PropertyNotFoundException, ELException {
053: if (context == null) {
054: throw new NullPointerException();
055: }
056: if (base == null || property == null) {
057: return null;
058: }
059:
060: context.setPropertyResolved(true);
061: Method m = this .property(context, base, property).read(context);
062: try {
063: return m.invoke(base, (Object[]) null);
064: } catch (IllegalAccessException e) {
065: throw new ELException(e);
066: } catch (InvocationTargetException e) {
067: throw new ELException(message(context, "propertyReadError",
068: new Object[] { base.getClass().getName(),
069: property.toString() }), e.getCause());
070: } catch (Exception e) {
071: throw new ELException(e);
072: }
073: }
074:
075: public Class<?> getType(ELContext context, Object base,
076: Object property) throws NullPointerException,
077: PropertyNotFoundException, ELException {
078: if (context == null) {
079: throw new NullPointerException();
080: }
081: if (base == null || property == null) {
082: return null;
083: }
084:
085: context.setPropertyResolved(true);
086: return this .property(context, base, property).getPropertyType();
087: }
088:
089: public void setValue(ELContext context, Object base,
090: Object property, Object value) throws NullPointerException,
091: PropertyNotFoundException, PropertyNotWritableException,
092: ELException {
093: if (context == null) {
094: throw new NullPointerException();
095: }
096: if (base == null || property == null) {
097: return;
098: }
099:
100: context.setPropertyResolved(true);
101:
102: if (this .readOnly) {
103: throw new PropertyNotWritableException(message(context,
104: "resolverNotWriteable", new Object[] { base
105: .getClass().getName() }));
106: }
107:
108: Method m = this .property(context, base, property)
109: .write(context);
110: try {
111: m.invoke(base, value);
112: } catch (IllegalAccessException e) {
113: throw new ELException(e);
114: } catch (InvocationTargetException e) {
115: throw new ELException(message(context,
116: "propertyWriteError", new Object[] {
117: base.getClass().getName(),
118: property.toString() }), e.getCause());
119: } catch (Exception e) {
120: throw new ELException(e);
121: }
122: }
123:
124: public boolean isReadOnly(ELContext context, Object base,
125: Object property) throws NullPointerException,
126: PropertyNotFoundException, ELException {
127: if (context == null) {
128: throw new NullPointerException();
129: }
130: if (base == null || property == null) {
131: return false;
132: }
133:
134: context.setPropertyResolved(true);
135: return this .readOnly
136: || this .property(context, base, property).isReadOnly();
137: }
138:
139: public Iterator<FeatureDescriptor> getFeatureDescriptors(
140: ELContext context, Object base) {
141: if (context == null) {
142: throw new NullPointerException();
143: }
144:
145: if (base == null) {
146: return null;
147: }
148:
149: try {
150: BeanInfo info = Introspector.getBeanInfo(base.getClass());
151: PropertyDescriptor[] pds = info.getPropertyDescriptors();
152: for (int i = 0; i < pds.length; i++) {
153: pds[i]
154: .setValue(RESOLVABLE_AT_DESIGN_TIME,
155: Boolean.TRUE);
156: pds[i].setValue(TYPE, pds[i].getPropertyType());
157: }
158: return Arrays.asList((FeatureDescriptor[]) pds).iterator();
159: } catch (IntrospectionException e) {
160: //
161: }
162:
163: return null;
164: }
165:
166: public Class<?> getCommonPropertyType(ELContext context, Object base) {
167: if (context == null) {
168: throw new NullPointerException();
169: }
170:
171: if (base != null) {
172: return Object.class;
173: }
174:
175: return null;
176: }
177:
178: protected final static class BeanProperties {
179: private final Map<String, BeanProperty> properties;
180:
181: private final Class<?> type;
182:
183: public BeanProperties(Class<?> type) throws ELException {
184: this .type = type;
185: this .properties = new HashMap<String, BeanProperty>();
186: try {
187: BeanInfo info = Introspector.getBeanInfo(this .type);
188: PropertyDescriptor[] pds = info
189: .getPropertyDescriptors();
190: for (int i = 0; i < pds.length; i++) {
191: this .properties.put(pds[i].getName(),
192: new BeanProperty(type, pds[i]));
193: }
194: } catch (IntrospectionException ie) {
195: throw new ELException(ie);
196: }
197: }
198:
199: private BeanProperty get(ELContext ctx, String name) {
200: BeanProperty property = this .properties.get(name);
201: if (property == null) {
202: throw new PropertyNotFoundException(message(ctx,
203: "propertyNotFound", new Object[] {
204: type.getName(), name }));
205: }
206: return property;
207: }
208:
209: public BeanProperty getBeanProperty(String name) {
210: return get(null, name);
211: }
212:
213: private Class<?> getType() {
214: return type;
215: }
216: }
217:
218: protected final static class BeanProperty {
219: private final Class type;
220:
221: private final Class owner;
222:
223: private final PropertyDescriptor descriptor;
224:
225: private Method read;
226:
227: private Method write;
228:
229: public BeanProperty(Class<?> owner,
230: PropertyDescriptor descriptor) {
231: this .owner = owner;
232: this .descriptor = descriptor;
233: this .type = descriptor.getPropertyType();
234: }
235:
236: public Class getPropertyType() {
237: return this .type;
238: }
239:
240: public boolean isReadOnly() {
241: return this .write == null
242: && (null == (this .write = getMethod(this .owner,
243: descriptor.getWriteMethod())));
244: }
245:
246: public Method getWriteMethod() {
247: return write(null);
248: }
249:
250: public Method getReadMethod() {
251: return this .read(null);
252: }
253:
254: private Method write(ELContext ctx) {
255: if (this .write == null) {
256: this .write = getMethod(this .owner, descriptor
257: .getWriteMethod());
258: if (this .write == null) {
259: throw new PropertyNotFoundException(message(ctx,
260: "propertyNotWritable", new Object[] {
261: type.getName(),
262: descriptor.getName() }));
263: }
264: }
265: return this .write;
266: }
267:
268: private Method read(ELContext ctx) {
269: if (this .read == null) {
270: this .read = getMethod(this .owner, descriptor
271: .getReadMethod());
272: if (this .read == null) {
273: throw new PropertyNotFoundException(message(ctx,
274: "propertyNotReadable", new Object[] {
275: type.getName(),
276: descriptor.getName() }));
277: }
278: }
279: return this .read;
280: }
281: }
282:
283: private final BeanProperty property(ELContext ctx, Object base,
284: Object property) {
285: Class<?> type = base.getClass();
286: String prop = property.toString();
287:
288: BeanProperties props = this .cache.get(type.getName());
289: if (props == null || type != props.getType()) {
290: props = new BeanProperties(type);
291: this .cache.put(type.getName(), props);
292: }
293:
294: return props.get(ctx, prop);
295: }
296:
297: private final static Method getMethod(Class type, Method m) {
298: if (m == null || Modifier.isPublic(type.getModifiers())) {
299: return m;
300: }
301: Class[] inf = type.getInterfaces();
302: Method mp = null;
303: for (int i = 0; i < inf.length; i++) {
304: try {
305: mp = inf[i].getMethod(m.getName(), (Class[]) m
306: .getParameterTypes());
307: mp = getMethod(mp.getDeclaringClass(), mp);
308: if (mp != null) {
309: return mp;
310: }
311: } catch (NoSuchMethodException e) {
312: }
313: }
314: Class sup = type.getSuperclass();
315: if (sup != null) {
316: try {
317: mp = sup.getMethod(m.getName(), (Class[]) m
318: .getParameterTypes());
319: mp = getMethod(mp.getDeclaringClass(), mp);
320: if (mp != null) {
321: return mp;
322: }
323: } catch (NoSuchMethodException e) {
324: }
325: }
326: return null;
327: }
328:
329: private final static class ConcurrentCache<K, V> {
330:
331: private final int size;
332: private final Map<K, V> eden;
333: private final Map<K, V> longterm;
334:
335: public ConcurrentCache(int size) {
336: this .size = size;
337: this .eden = new ConcurrentHashMap<K, V>(size);
338: this .longterm = new WeakHashMap<K, V>(size);
339: }
340:
341: public V get(K key) {
342: V value = this .eden.get(key);
343: if (value == null) {
344: value = this .longterm.get(key);
345: if (value != null) {
346: this .eden.put(key, value);
347: }
348: }
349: return value;
350: }
351:
352: public void put(K key, V value) {
353: if (this.eden.size() >= this.size) {
354: this.longterm.putAll(this.eden);
355: this.eden.clear();
356: }
357: this.eden.put(key, value);
358: }
359:
360: }
361: }
|