• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
16 package org.mockito.cglib.beans;
17 
18 import java.beans.*;
19 import java.lang.reflect.Constructor;
20 import java.lang.reflect.Method;
21 import java.util.*;
22 
23 import org.mockito.asm.ClassVisitor;
24 import 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  */
34 abstract 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      */
create(Object bean)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 {
newInstance(Class type, int require)69             public Object newInstance(Class type, int require);
70         }
71 
72         private Object bean;
73         private Class beanClass;
74         private int require;
75 
Generator()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          */
setBean(Object bean)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          */
setBeanClass(Class beanClass)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          */
setRequire(int require)107         public void setRequire(int require) {
108             this.require = require;
109         }
110 
getDefaultClassLoader()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          */
create()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 
generateClass(ClassVisitor v)126         public void generateClass(ClassVisitor v) throws Exception {
127             new BeanMapEmitter(v, getClassName(), beanClass, require);
128         }
129 
firstInstance(Class type)130         protected Object firstInstance(Class type) {
131             return ((BeanMap)ReflectUtils.newInstance(type)).newInstance(bean);
132         }
133 
nextInstance(Object instance)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      */
newInstance(Object bean)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      */
getPropertyType(String name)152     abstract public Class getPropertyType(String name);
153 
154     protected Object bean;
155 
BeanMap()156     protected BeanMap() {
157     }
158 
BeanMap(Object bean)159     protected BeanMap(Object bean) {
160         setBean(bean);
161     }
162 
get(Object key)163     public Object get(Object key) {
164         return get(bean, key);
165     }
166 
put(Object key, Object value)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      */
get(Object bean, Object key)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      */
put(Object bean, Object key, Object value)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      */
setBean(Object bean)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      */
getBean()205     public Object getBean() {
206         return bean;
207     }
208 
clear()209     public void clear() {
210         throw new UnsupportedOperationException();
211     }
212 
containsKey(Object key)213     public boolean containsKey(Object key) {
214         return keySet().contains(key);
215     }
216 
containsValue(Object value)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 
size()226     public int size() {
227         return keySet().size();
228     }
229 
isEmpty()230     public boolean isEmpty() {
231         return size() == 0;
232     }
233 
remove(Object key)234     public Object remove(Object key) {
235         throw new UnsupportedOperationException();
236     }
237 
putAll(Map t)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 
equals(Object o)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 
hashCode()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
entrySet()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 
values()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      */
toString()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