1/*
2 * Copyright 2003,2004 The Apache Software Foundation
3 *
4 *  Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 *  Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.mockito.cglib.beans;
17
18import java.beans.*;
19import java.lang.reflect.Constructor;
20import java.lang.reflect.Method;
21import java.util.*;
22
23import org.mockito.asm.ClassVisitor;
24import org.mockito.cglib.core.*;
25
26/**
27 * A <code>Map</code>-based view of a JavaBean.  The default set of keys is the
28 * union of all property names (getters or setters). An attempt to set
29 * a read-only property will be ignored, and write-only properties will
30 * be returned as <code>null</code>. Removal of objects is not a
31 * supported (the key set is fixed).
32 * @author Chris Nokleberg
33 */
34abstract public class BeanMap implements Map {
35    /**
36     * Limit the properties reflected in the key set of the map
37     * to readable properties.
38     * @see BeanMap.Generator#setRequire
39     */
40    public static final int REQUIRE_GETTER = 1;
41
42    /**
43     * Limit the properties reflected in the key set of the map
44     * to writable properties.
45     * @see BeanMap.Generator#setRequire
46     */
47    public static final int REQUIRE_SETTER = 2;
48
49    /**
50     * Helper method to create a new <code>BeanMap</code>.  For finer
51     * control over the generated instance, use a new instance of
52     * <code>BeanMap.Generator</code> instead of this static method.
53     * @param bean the JavaBean underlying the map
54     * @return a new <code>BeanMap</code> instance
55     */
56    public static BeanMap create(Object bean) {
57        Generator gen = new Generator();
58        gen.setBean(bean);
59        return gen.create();
60    }
61
62    public static class Generator extends AbstractClassGenerator {
63        private static final Source SOURCE = new Source(BeanMap.class.getName());
64
65        private static final BeanMapKey KEY_FACTORY =
66          (BeanMapKey)KeyFactory.create(BeanMapKey.class, KeyFactory.CLASS_BY_NAME);
67
68        interface BeanMapKey {
69            public Object newInstance(Class type, int require);
70        }
71
72        private Object bean;
73        private Class beanClass;
74        private int require;
75
76        public Generator() {
77            super(SOURCE);
78        }
79
80        /**
81         * Set the bean that the generated map should reflect. The bean may be swapped
82         * out for another bean of the same type using {@link #setBean}.
83         * Calling this method overrides any value previously set using {@link #setBeanClass}.
84         * You must call either this method or {@link #setBeanClass} before {@link #create}.
85         * @param bean the initial bean
86         */
87        public void setBean(Object bean) {
88            this.bean = bean;
89            if (bean != null)
90                beanClass = bean.getClass();
91        }
92
93        /**
94         * Set the class of the bean that the generated map should support.
95         * You must call either this method or {@link #setBeanClass} before {@link #create}.
96         * @param beanClass the class of the bean
97         */
98        public void setBeanClass(Class beanClass) {
99            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("Class of bean unknown");
122            setNamePrefix(beanClass.getName());
123            return (BeanMap)super.create(KEY_FACTORY.newInstance(beanClass, require));
124        }
125
126        public void generateClass(ClassVisitor v) throws Exception {
127            new BeanMapEmitter(v, getClassName(), beanClass, require);
128        }
129
130        protected Object firstInstance(Class type) {
131            return ((BeanMap)ReflectUtils.newInstance(type)).newInstance(bean);
132        }
133
134        protected Object nextInstance(Object instance) {
135            return ((BeanMap)instance).newInstance(bean);
136        }
137    }
138
139    /**
140     * Create a new <code>BeanMap</code> instance using the specified bean.
141     * This is faster than using the {@link #create} static method.
142     * @param bean the JavaBean underlying the map
143     * @return a new <code>BeanMap</code> instance
144     */
145    abstract public BeanMap newInstance(Object bean);
146
147    /**
148     * Get the type of a property.
149     * @param name the name of the JavaBean property
150     * @return the type of the property, or null if the property does not exist
151     */
152    abstract public Class getPropertyType(String name);
153
154    protected Object bean;
155
156    protected BeanMap() {
157    }
158
159    protected BeanMap(Object bean) {
160        setBean(bean);
161    }
162
163    public Object get(Object key) {
164        return get(bean, key);
165    }
166
167    public Object put(Object key, Object value) {
168        return put(bean, key, value);
169    }
170
171    /**
172     * Get the property of a bean. This allows a <code>BeanMap</code>
173     * to be used statically for multiple beans--the bean instance tied to the
174     * map is ignored and the bean passed to this method is used instead.
175     * @param bean the bean to query; must be compatible with the type of
176     * this <code>BeanMap</code>
177     * @param key must be a String
178     * @return the current value, or null if there is no matching property
179     */
180    abstract public Object get(Object bean, Object key);
181
182    /**
183     * Set the property of a bean. This allows a <code>BeanMap</code>
184     * to be used statically for multiple beans--the bean instance tied to the
185     * map is ignored and the bean passed to this method is used instead.
186     * @param key must be a String
187     * @return the old value, if there was one, or null
188     */
189    abstract public Object put(Object bean, Object key, Object value);
190
191    /**
192     * Change the underlying bean this map should use.
193     * @param bean the new JavaBean
194     * @see #getBean
195     */
196    public void setBean(Object bean) {
197        this.bean = bean;
198    }
199
200    /**
201     * Return the bean currently in use by this map.
202     * @return the current JavaBean
203     * @see #setBean
204     */
205    public Object getBean() {
206        return bean;
207    }
208
209    public void clear() {
210        throw new UnsupportedOperationException();
211    }
212
213    public boolean containsKey(Object key) {
214        return keySet().contains(key);
215    }
216
217    public boolean containsValue(Object value) {
218        for (Iterator it = keySet().iterator(); it.hasNext();) {
219            Object v = get(it.next());
220            if (((value == null) && (v == null)) || value.equals(v))
221                return true;
222        }
223        return false;
224    }
225
226    public int size() {
227        return keySet().size();
228    }
229
230    public boolean isEmpty() {
231        return size() == 0;
232    }
233
234    public Object remove(Object key) {
235        throw new UnsupportedOperationException();
236    }
237
238    public void putAll(Map t) {
239        for (Iterator it = t.keySet().iterator(); it.hasNext();) {
240            Object key = it.next();
241            put(key, t.get(key));
242        }
243    }
244
245    public boolean equals(Object o) {
246        if (o == null || !(o instanceof Map)) {
247            return false;
248        }
249        Map other = (Map)o;
250        if (size() != other.size()) {
251            return false;
252        }
253        for (Iterator it = keySet().iterator(); it.hasNext();) {
254            Object key = it.next();
255            if (!other.containsKey(key)) {
256                return false;
257            }
258            Object v1 = get(key);
259            Object v2 = other.get(key);
260            if (!((v1 == null) ? v2 == null : v1.equals(v2))) {
261                return false;
262            }
263        }
264        return true;
265    }
266
267    public int hashCode() {
268        int code = 0;
269        for (Iterator it = keySet().iterator(); it.hasNext();) {
270            Object key = it.next();
271            Object value = get(key);
272            code += ((key == null) ? 0 : key.hashCode()) ^
273                ((value == null) ? 0 : value.hashCode());
274        }
275        return code;
276    }
277
278    // TODO: optimize
279    public Set entrySet() {
280        HashMap copy = new HashMap();
281        for (Iterator it = keySet().iterator(); it.hasNext();) {
282            Object key = it.next();
283            copy.put(key, get(key));
284        }
285        return Collections.unmodifiableMap(copy).entrySet();
286    }
287
288    public Collection values() {
289        Set keys = keySet();
290        List values = new ArrayList(keys.size());
291        for (Iterator it = keys.iterator(); it.hasNext();) {
292            values.add(get(it.next()));
293        }
294        return Collections.unmodifiableCollection(values);
295    }
296
297    /*
298     * @see java.util.AbstractMap#toString
299     */
300    public String toString()
301    {
302        StringBuffer sb = new StringBuffer();
303        sb.append('{');
304        for (Iterator it = keySet().iterator(); it.hasNext();) {
305            Object key = it.next();
306            sb.append(key);
307            sb.append('=');
308            sb.append(get(key));
309            if (it.hasNext()) {
310                sb.append(", ");
311            }
312        }
313        sb.append('}');
314        return sb.toString();
315    }
316}
317