BeanMap.java revision 674060f01e9090cd21b3c5656cc3204912ad17a6
15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/*
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Copyright 2003,2004 The Apache Software Foundation
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *  Licensed under the Apache License, Version 2.0 (the "License");
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * you may not use this file except in compliance with the License.
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * You may obtain a copy of the License at
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *      http://www.apache.org/licenses/LICENSE-2.0
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) *  Unless required by applicable law or agreed to in writing, software
1190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) * distributed under the License is distributed on an "AS IS" BASIS,
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * See the License for the specific language governing permissions and
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * limitations under the License.
150f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) */
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)package org.mockito.cglib.beans;
170f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)
180f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)import java.beans.*;
190f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)import java.lang.reflect.Constructor;
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import java.lang.reflect.Method;
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import java.util.*;
22c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
23c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)import org.mockito.asm.ClassVisitor;
24c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)import org.mockito.cglib.core.*;
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
270f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * A <code>Map</code>-based view of a JavaBean.  The default set of keys is the
280f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * union of all property names (getters or setters). An attempt to set
290f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * a read-only property will be ignored, and write-only properties will
300f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * be returned as <code>null</code>. Removal of objects is not a
310f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * supported (the key set is fixed).
320f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) * @author Chris Nokleberg
330f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) */
340f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)abstract public class BeanMap implements Map {
350f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)    /**
360f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)     * Limit the properties reflected in the key set of the map
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * to readable properties.
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * @see BeanMap.Generator#setRequire
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     */
400f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)    public static final int REQUIRE_GETTER = 1;
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /**
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * Limit the properties reflected in the key set of the map
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * to writable properties.
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * @see BeanMap.Generator#setRequire
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     */
470f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)    public static final int REQUIRE_SETTER = 2;
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    /**
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * Helper method to create a new <code>BeanMap</code>.  For finer
510f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)     * control over the generated instance, use a new instance of
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * <code>BeanMap.Generator</code> instead of this static method.
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * @param bean the JavaBean underlying the map
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     * @return a new <code>BeanMap</code> instance
55f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)     */
56f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    public static BeanMap create(Object bean) {
57f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)        Generator gen = new Generator();
58f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)        gen.setBean(bean);
590f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)        return gen.create();
60c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    }
61c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
6290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    public static class Generator extends AbstractClassGenerator {
6390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)        private static final Source SOURCE = new Source(BeanMap.class.getName());
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        private static final BeanMapKey KEY_FACTORY =
660f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)          (BeanMapKey)KeyFactory.create(BeanMapKey.class, KeyFactory.CLASS_BY_NAME);
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        interface BeanMapKey {
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            public Object newInstance(Class type, int require);
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        }
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