001: /*
002: * Copyright 2003,2004 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package net.sf.cglib.beans;
017:
018: import java.beans.*;
019: import java.lang.reflect.Constructor;
020: import java.lang.reflect.Method;
021: import java.util.*;
022: import net.sf.cglib.core.*;
023: import org.objectweb.asm.ClassVisitor;
024:
025: /**
026: * A <code>Map</code>-based view of a JavaBean. The default set of keys is the
027: * union of all property names (getters or setters). An attempt to set
028: * a read-only property will be ignored, and write-only properties will
029: * be returned as <code>null</code>. Removal of objects is not a
030: * supported (the key set is fixed).
031: * @author Chris Nokleberg
032: */
033: abstract public class BeanMap implements Map {
034: /**
035: * Limit the properties reflected in the key set of the map
036: * to readable properties.
037: * @see BeanMap.Generator#setRequire
038: */
039: public static final int REQUIRE_GETTER = 1;
040:
041: /**
042: * Limit the properties reflected in the key set of the map
043: * to writable properties.
044: * @see BeanMap.Generator#setRequire
045: */
046: public static final int REQUIRE_SETTER = 2;
047:
048: /**
049: * Helper method to create a new <code>BeanMap</code>. For finer
050: * control over the generated instance, use a new instance of
051: * <code>BeanMap.Generator</code> instead of this static method.
052: * @param bean the JavaBean underlying the map
053: * @return a new <code>BeanMap</code> instance
054: */
055: public static BeanMap create(Object bean) {
056: Generator gen = new Generator();
057: gen.setBean(bean);
058: return gen.create();
059: }
060:
061: public static class Generator extends AbstractClassGenerator {
062: private static final Source SOURCE = new Source(BeanMap.class
063: .getName());
064:
065: private static final BeanMapKey KEY_FACTORY = (BeanMapKey) KeyFactory
066: .create(BeanMapKey.class, KeyFactory.CLASS_BY_NAME);
067:
068: interface BeanMapKey {
069: public Object newInstance(Class type, int require);
070: }
071:
072: private Object bean;
073: private Class beanClass;
074: private int require;
075:
076: public Generator() {
077: super (SOURCE);
078: }
079:
080: /**
081: * Set the bean that the generated map should reflect. The bean may be swapped
082: * out for another bean of the same type using {@link #setBean}.
083: * Calling this method overrides any value previously set using {@link #setBeanClass}.
084: * You must call either this method or {@link #setBeanClass} before {@link #create}.
085: * @param bean the initial bean
086: */
087: public void setBean(Object bean) {
088: this .bean = bean;
089: if (bean != null)
090: beanClass = bean.getClass();
091: }
092:
093: /**
094: * Set the class of the bean that the generated map should support.
095: * You must call either this method or {@link #setBeanClass} before {@link #create}.
096: * @param beanClass the class of the bean
097: */
098: public void setBeanClass(Class beanClass) {
099: this .beanClass = beanClass;
100: }
101:
102: /**
103: * Limit the properties reflected by the generated map.
104: * @param require any combination of {@link #REQUIRE_GETTER} and
105: * {@link #REQUIRE_SETTER}; default is zero (any property allowed)
106: */
107: public void setRequire(int require) {
108: this .require = require;
109: }
110:
111: protected ClassLoader getDefaultClassLoader() {
112: return beanClass.getClassLoader();
113: }
114:
115: /**
116: * Create a new instance of the <code>BeanMap</code>. An existing
117: * generated class will be reused if possible.
118: */
119: public BeanMap create() {
120: if (beanClass == null)
121: throw new IllegalArgumentException(
122: "Class of bean unknown");
123: setNamePrefix(beanClass.getName());
124: return (BeanMap) super .create(KEY_FACTORY.newInstance(
125: beanClass, require));
126: }
127:
128: public void generateClass(ClassVisitor v) throws Exception {
129: new BeanMapEmitter(v, getClassName(), beanClass, require);
130: }
131:
132: protected Object firstInstance(Class type) {
133: return ((BeanMap) ReflectUtils.newInstance(type))
134: .newInstance(bean);
135: }
136:
137: protected Object nextInstance(Object instance) {
138: return ((BeanMap) instance).newInstance(bean);
139: }
140: }
141:
142: /**
143: * Create a new <code>BeanMap</code> instance using the specified bean.
144: * This is faster than using the {@link #create} static method.
145: * @param bean the JavaBean underlying the map
146: * @return a new <code>BeanMap</code> instance
147: */
148: abstract public BeanMap newInstance(Object bean);
149:
150: /**
151: * Get the type of a property.
152: * @param name the name of the JavaBean property
153: * @return the type of the property, or null if the property does not exist
154: */
155: abstract public Class getPropertyType(String name);
156:
157: protected Object bean;
158:
159: protected BeanMap() {
160: }
161:
162: protected BeanMap(Object bean) {
163: setBean(bean);
164: }
165:
166: public Object get(Object key) {
167: return get(bean, key);
168: }
169:
170: public Object put(Object key, Object value) {
171: return put(bean, key, value);
172: }
173:
174: /**
175: * Get the property of a bean. This allows a <code>BeanMap</code>
176: * to be used statically for multiple beans--the bean instance tied to the
177: * map is ignored and the bean passed to this method is used instead.
178: * @param bean the bean to query; must be compatible with the type of
179: * this <code>BeanMap</code>
180: * @param key must be a String
181: * @return the current value, or null if there is no matching property
182: */
183: abstract public Object get(Object bean, Object key);
184:
185: /**
186: * Set the property of a bean. This allows a <code>BeanMap</code>
187: * to be used statically for multiple beans--the bean instance tied to the
188: * map is ignored and the bean passed to this method is used instead.
189: * @param key must be a String
190: * @return the old value, if there was one, or null
191: */
192: abstract public Object put(Object bean, Object key, Object value);
193:
194: /**
195: * Change the underlying bean this map should use.
196: * @param bean the new JavaBean
197: * @see #getBean
198: */
199: public void setBean(Object bean) {
200: this .bean = bean;
201: }
202:
203: /**
204: * Return the bean currently in use by this map.
205: * @return the current JavaBean
206: * @see #setBean
207: */
208: public Object getBean() {
209: return bean;
210: }
211:
212: public void clear() {
213: throw new UnsupportedOperationException();
214: }
215:
216: public boolean containsKey(Object key) {
217: return keySet().contains(key);
218: }
219:
220: public boolean containsValue(Object value) {
221: for (Iterator it = keySet().iterator(); it.hasNext();) {
222: Object v = get(it.next());
223: if (((value == null) && (v == null)) || value.equals(v))
224: return true;
225: }
226: return false;
227: }
228:
229: public int size() {
230: return keySet().size();
231: }
232:
233: public boolean isEmpty() {
234: return size() == 0;
235: }
236:
237: public Object remove(Object key) {
238: throw new UnsupportedOperationException();
239: }
240:
241: public void putAll(Map t) {
242: for (Iterator it = t.keySet().iterator(); it.hasNext();) {
243: Object key = it.next();
244: put(key, t.get(key));
245: }
246: }
247:
248: public boolean equals(Object o) {
249: if (o == null || !(o instanceof Map)) {
250: return false;
251: }
252: Map other = (Map) o;
253: if (size() != other.size()) {
254: return false;
255: }
256: for (Iterator it = keySet().iterator(); it.hasNext();) {
257: Object key = it.next();
258: if (!other.containsKey(key)) {
259: return false;
260: }
261: Object v1 = get(key);
262: Object v2 = other.get(key);
263: if (!((v1 == null) ? v2 == null : v1.equals(v2))) {
264: return false;
265: }
266: }
267: return true;
268: }
269:
270: public int hashCode() {
271: int code = 0;
272: for (Iterator it = keySet().iterator(); it.hasNext();) {
273: Object key = it.next();
274: Object value = get(key);
275: code += ((key == null) ? 0 : key.hashCode())
276: ^ ((value == null) ? 0 : value.hashCode());
277: }
278: return code;
279: }
280:
281: // TODO: optimize
282: public Set entrySet() {
283: HashMap copy = new HashMap();
284: for (Iterator it = keySet().iterator(); it.hasNext();) {
285: Object key = it.next();
286: copy.put(key, get(key));
287: }
288: return Collections.unmodifiableMap(copy).entrySet();
289: }
290:
291: public Collection values() {
292: Set keys = keySet();
293: List values = new ArrayList(keys.size());
294: for (Iterator it = keys.iterator(); it.hasNext();) {
295: values.add(get(it.next()));
296: }
297: return Collections.unmodifiableCollection(values);
298: }
299:
300: /*
301: * @see java.util.AbstractMap#toString
302: */
303: public String toString() {
304: StringBuffer sb = new StringBuffer();
305: sb.append('{');
306: for (Iterator it = keySet().iterator(); it.hasNext();) {
307: Object key = it.next();
308: sb.append(key);
309: sb.append('=');
310: sb.append(get(key));
311: if (it.hasNext()) {
312: sb.append(", ");
313: }
314: }
315: sb.append('}');
316: return sb.toString();
317: }
318: }
|