001: /*
002: * The contents of this file are subject to the terms
003: * of the Common Development and Distribution License
004: * (the "License"). You may not use this file except
005: * in compliance with the License.
006: *
007: * You can obtain a copy of the license at
008: * glassfish/bootstrap/legal/CDDLv1.0.txt or
009: * https://glassfish.dev.java.net/public/CDDLv1.0.html.
010: * See the License for the specific language governing
011: * permissions and limitations under the License.
012: *
013: * When distributing Covered Code, include this CDDL
014: * HEADER in each file and include the License file at
015: * glassfish/bootstrap/legal/CDDLv1.0.txt. If applicable,
016: * add the following below this CDDL HEADER, with the
017: * fields enclosed by brackets "[]" replaced with your
018: * own identifying information: Portions Copyright [yyyy]
019: * [name of copyright owner]
020: *
021: * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
022: */
023:
024: package javax.el;
025:
026: import java.beans.FeatureDescriptor;
027: import java.util.Map;
028: import java.util.Iterator;
029: import java.util.Collections;
030: import java.util.HashMap;
031: import java.util.List;
032: import java.util.ArrayList;
033:
034: /**
035: * Defines property resolution behavior on instances of {@link java.util.Map}.
036: *
037: * <p>This resolver handles base objects of type <code>java.util.Map</code>.
038: * It accepts any object as a property and uses that object as a key in
039: * the map. The resulting value is the value in the map that is associated with
040: * that key.</p>
041: *
042: * <p>This resolver can be constructed in read-only mode, which means that
043: * {@link #isReadOnly} will always return <code>true</code> and
044: * {@link #setValue} will always throw
045: * <code>PropertyNotWritableException</code>.</p>
046: *
047: * <p><code>ELResolver</code>s are combined together using
048: * {@link CompositeELResolver}s, to define rich semantics for evaluating
049: * an expression. See the javadocs for {@link ELResolver} for details.</p>
050: *
051: * @see CompositeELResolver
052: * @see ELResolver
053: * @see java.util.Map
054: * @since JSP 2.1
055: */
056: public class MapELResolver extends ELResolver {
057:
058: /**
059: * Creates a new read/write <code>MapELResolver</code>.
060: */
061: public MapELResolver() {
062: this .isReadOnly = false;
063: }
064:
065: /**
066: * Creates a new <code>MapELResolver</code> whose read-only status is
067: * determined by the given parameter.
068: *
069: * @param isReadOnly <code>true</code> if this resolver cannot modify
070: * maps; <code>false</code> otherwise.
071: */
072: public MapELResolver(boolean isReadOnly) {
073: this .isReadOnly = isReadOnly;
074: }
075:
076: /**
077: * If the base object is a map, returns the most general acceptable type
078: * for a value in this map.
079: *
080: * <p>If the base is a <code>Map</code>, the <code>propertyResolved</code>
081: * property of the <code>ELContext</code> object must be set to
082: * <code>true</code> by this resolver, before returning. If this property
083: * is not <code>true</code> after this method is called, the caller
084: * should ignore the return value.</p>
085: *
086: * <p>Assuming the base is a <code>Map</code>, this method will always
087: * return <code>Object.class</code>. This is because <code>Map</code>s
088: * accept any object as the value for a given key.</p>
089: *
090: * @param context The context of this evaluation.
091: * @param base The map to analyze. Only bases of type <code>Map</code>
092: * are handled by this resolver.
093: * @param property The key to return the acceptable type for.
094: * Ignored by this resolver.
095: * @return If the <code>propertyResolved</code> property of
096: * <code>ELContext</code> was set to <code>true</code>, then
097: * the most general acceptable type; otherwise undefined.
098: * @throws NullPointerException if context is <code>null</code>
099: * @throws ELException if an exception was thrown while performing
100: * the property or variable resolution. The thrown exception
101: * must be included as the cause property of this exception, if
102: * available.
103: */
104: public Class<?> getType(ELContext context, Object base,
105: Object property) {
106:
107: if (context == null) {
108: throw new NullPointerException();
109: }
110:
111: if (base != null && base instanceof Map) {
112: context.setPropertyResolved(true);
113: return Object.class;
114: }
115: return null;
116: }
117:
118: /**
119: * If the base object is a map, returns the value associated with the
120: * given key, as specified by the <code>property</code> argument. If the
121: * key was not found, <code>null</code> is returned.
122: *
123: * <p>If the base is a <code>Map</code>, the <code>propertyResolved</code>
124: * property of the <code>ELContext</code> object must be set to
125: * <code>true</code> by this resolver, before returning. If this property
126: * is not <code>true</code> after this method is called, the caller
127: * should ignore the return value.</p>
128: *
129: * <p>Just as in {@link java.util.Map#get}, just because <code>null</code>
130: * is returned doesn't mean there is no mapping for the key; it's also
131: * possible that the <code>Map</code> explicitly maps the key to
132: * <code>null</code>.</p>
133: *
134: * @param context The context of this evaluation.
135: * @param base The map to be analyzed. Only bases of type <code>Map</code>
136: * are handled by this resolver.
137: * @param property The key whose associated value is to be returned.
138: * @return If the <code>propertyResolved</code> property of
139: * <code>ELContext</code> was set to <code>true</code>, then
140: * the value associated with the given key or <code>null</code>
141: * if the key was not found. Otherwise, undefined.
142: * @throws ClassCastException if the key is of an inappropriate type
143: * for this map (optionally thrown by the underlying <code>Map</code>).
144: * @throws NullPointerException if context is <code>null</code>, or if
145: * the key is null and this map does not permit null keys (the
146: * latter is optionally thrown by the underlying <code>Map</code>).
147: * @throws ELException if an exception was thrown while performing
148: * the property or variable resolution. The thrown exception
149: * must be included as the cause property of this exception, if
150: * available.
151: */
152: public Object getValue(ELContext context, Object base,
153: Object property) {
154:
155: if (context == null) {
156: throw new NullPointerException();
157: }
158:
159: if (base != null && base instanceof Map) {
160: context.setPropertyResolved(true);
161: Map map = (Map) base;
162: return map.get(property);
163: }
164: return null;
165: }
166:
167: static private Class<?> theUnmodifiableMapClass = Collections
168: .unmodifiableMap(new HashMap()).getClass();
169:
170: /**
171: * If the base object is a map, attempts to set the value associated with
172: * the given key, as specified by the <code>property</code> argument.
173: *
174: * <p>If the base is a <code>Map</code>, the <code>propertyResolved</code>
175: * property of the <code>ELContext</code> object must be set to
176: * <code>true</code> by this resolver, before returning. If this property
177: * is not <code>true</code> after this method is called, the caller
178: * can safely assume no value was set.</p>
179: *
180: * <p>If this resolver was constructed in read-only mode, this method will
181: * always throw <code>PropertyNotWritableException</code>.</p>
182: *
183: * <p>If a <code>Map</code> was created using
184: * {@link java.util.Collections#unmodifiableMap}, this method must
185: * throw <code>PropertyNotWritableException</code>. Unfortunately,
186: * there is no Collections API method to detect this. However, an
187: * implementation can create a prototype unmodifiable <code>Map</code>
188: * and query its runtime type to see if it matches the runtime type of
189: * the base object as a workaround.</p>
190: *
191: * @param context The context of this evaluation.
192: * @param base The map to be modified. Only bases of type <code>Map</code>
193: * are handled by this resolver.
194: * @param property The key with which the specified value is to be
195: * associated.
196: * @param val The value to be associated with the specified key.
197: * @throws ClassCastException if the class of the specified key or
198: * value prevents it from being stored in this map.
199: * @throws NullPointerException if context is <code>null</code>, or if
200: * this map does not permit <code>null</code> keys or values, and
201: * the specified key or value is <code>null</code>.
202: * @throws IllegalArgumentException if some aspect of this key or
203: * value prevents it from being stored in this map.
204: * @throws ELException if an exception was thrown while performing
205: * the property or variable resolution. The thrown exception
206: * must be included as the cause property of this exception, if
207: * available.
208: * @throws PropertyNotWritableException if this resolver was constructed
209: * in read-only mode, or if the put operation is not supported by
210: * the underlying map.
211: */
212: public void setValue(ELContext context, Object base,
213: Object property, Object val) {
214:
215: if (context == null) {
216: throw new NullPointerException();
217: }
218:
219: if (base != null && base instanceof Map) {
220: context.setPropertyResolved(true);
221: Map map = (Map) base;
222: if (isReadOnly || map.getClass() == theUnmodifiableMapClass) {
223: throw new PropertyNotWritableException();
224: }
225: try {
226: map.put(property, val);
227: } catch (UnsupportedOperationException ex) {
228: throw new PropertyNotWritableException();
229: }
230: }
231: }
232:
233: /**
234: * If the base object is a map, returns whether a call to
235: * {@link #setValue} will always fail.
236: *
237: * <p>If the base is a <code>Map</code>, the <code>propertyResolved</code>
238: * property of the <code>ELContext</code> object must be set to
239: * <code>true</code> by this resolver, before returning. If this property
240: * is not <code>true</code> after this method is called, the caller
241: * should ignore the return value.</p>
242: *
243: * <p>If this resolver was constructed in read-only mode, this method will
244: * always return <code>true</code>.</p>
245: *
246: * <p>If a <code>Map</code> was created using
247: * {@link java.util.Collections#unmodifiableMap}, this method must
248: * return <code>true</code>. Unfortunately, there is no Collections API
249: * method to detect this. However, an implementation can create a
250: * prototype unmodifiable <code>Map</code> and query its runtime type
251: * to see if it matches the runtime type of the base object as a
252: * workaround.</p>
253: *
254: * @param context The context of this evaluation.
255: * @param base The map to analyze. Only bases of type <code>Map</code>
256: * are handled by this resolver.
257: * @param property The key to return the read-only status for.
258: * Ignored by this resolver.
259: * @return If the <code>propertyResolved</code> property of
260: * <code>ELContext</code> was set to <code>true</code>, then
261: * <code>true</code> if calling the <code>setValue</code> method
262: * will always fail or <code>false</code> if it is possible that
263: * such a call may succeed; otherwise undefined.
264: * @throws NullPointerException if context is <code>null</code>
265: * @throws ELException if an exception was thrown while performing
266: * the property or variable resolution. The thrown exception
267: * must be included as the cause property of this exception, if
268: * available.
269: */
270: public boolean isReadOnly(ELContext context, Object base,
271: Object property) {
272:
273: if (context == null) {
274: throw new NullPointerException();
275: }
276:
277: if (base != null && base instanceof Map) {
278: context.setPropertyResolved(true);
279: Map map = (Map) base;
280: return isReadOnly
281: || map.getClass() == theUnmodifiableMapClass;
282: }
283: return false;
284: }
285:
286: /**
287: * If the base object is a map, returns an <code>Iterator</code>
288: * containing the set of keys available in the <code>Map</code>.
289: * Otherwise, returns <code>null</code>.
290: *
291: * <p>The <code>Iterator</code> returned must contain zero or more
292: * instances of {@link java.beans.FeatureDescriptor}. Each info object
293: * contains information about a key in the Map, and is initialized as
294: * follows:
295: * <dl>
296: * <li>displayName - The return value of calling the
297: * <code>toString</code> method on this key, or
298: * <code>"null"</code> if the key is <code>null</code>.</li>
299: * <li>name - Same as displayName property.</li>
300: * <li>shortDescription - Empty string</li>
301: * <li>expert - <code>false</code></li>
302: * <li>hidden - <code>false</code></li>
303: * <li>preferred - <code>true</code></li>
304: * </dl>
305: * In addition, the following named attributes must be set in the
306: * returned <code>FeatureDescriptor</code>s:
307: * <dl>
308: * <li>{@link ELResolver#TYPE} - The return value of calling the <code>getClass()</code>
309: * method on this key, or <code>null</code> if the key is
310: * <code>null</code>.</li>
311: * <li>{@link ELResolver#RESOLVABLE_AT_DESIGN_TIME} - <code>true</code></li>
312: * </dl>
313: * </p>
314: *
315: * @param context The context of this evaluation.
316: * @param base The map whose keys are to be iterated over. Only bases
317: * of type <code>Map</code> are handled by this resolver.
318: * @return An <code>Iterator</code> containing zero or more (possibly
319: * infinitely more) <code>FeatureDescriptor</code> objects, each
320: * representing a key in this map, or <code>null</code> if
321: * the base object is not a map.
322: */
323: public Iterator<FeatureDescriptor> getFeatureDescriptors(
324: ELContext context, Object base) {
325:
326: if (base != null && base instanceof Map) {
327: Map map = (Map) base;
328: Iterator iter = map.keySet().iterator();
329: List<FeatureDescriptor> list = new ArrayList<FeatureDescriptor>();
330: while (iter.hasNext()) {
331: Object key = iter.next();
332: FeatureDescriptor descriptor = new FeatureDescriptor();
333: String name = (key == null) ? null : key.toString();
334: descriptor.setName(name);
335: descriptor.setDisplayName(name);
336: descriptor.setShortDescription("");
337: descriptor.setExpert(false);
338: descriptor.setHidden(false);
339: descriptor.setPreferred(true);
340: descriptor.setValue("type", key == null ? null : key
341: .getClass());
342: descriptor.setValue("resolvableAtDesignTime",
343: Boolean.TRUE);
344: list.add(descriptor);
345: }
346: return list.iterator();
347: }
348: return null;
349: }
350:
351: /**
352: * If the base object is a map, returns the most general type that
353: * this resolver accepts for the <code>property</code> argument.
354: * Otherwise, returns <code>null</code>.
355: *
356: * <p>Assuming the base is a <code>Map</code>, this method will always
357: * return <code>Object.class</code>. This is because <code>Map</code>s
358: * accept any object as a key.</p>
359: *
360: * @param context The context of this evaluation.
361: * @param base The map to analyze. Only bases of type <code>Map</code>
362: * are handled by this resolver.
363: * @return <code>null</code> if base is not a <code>Map</code>; otherwise
364: * <code>Object.class</code>.
365: */
366: public Class<?> getCommonPropertyType(ELContext context, Object base) {
367: if (base != null && base instanceof Map) {
368: return Object.class;
369: }
370: return null;
371: }
372:
373: private boolean isReadOnly;
374: }
|