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