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.util.*;
20
21import org.mockito.asm.ClassVisitor;
22import org.mockito.asm.Label;
23import org.mockito.asm.Type;
24import org.mockito.cglib.core.*;
25
26class BeanMapEmitter extends ClassEmitter {
27    private static final Type BEAN_MAP =
28      TypeUtils.parseType("org.mockito.cglib.beans.BeanMap");
29    private static final Type FIXED_KEY_SET =
30      TypeUtils.parseType("org.mockito.cglib.beans.FixedKeySet");
31    private static final Signature CSTRUCT_OBJECT =
32      TypeUtils.parseConstructor("Object");
33    private static final Signature CSTRUCT_STRING_ARRAY =
34      TypeUtils.parseConstructor("String[]");
35    private static final Signature BEAN_MAP_GET =
36      TypeUtils.parseSignature("Object get(Object, Object)");
37    private static final Signature BEAN_MAP_PUT =
38      TypeUtils.parseSignature("Object put(Object, Object, Object)");
39    private static final Signature KEY_SET =
40      TypeUtils.parseSignature("java.util.Set keySet()");
41    private static final Signature NEW_INSTANCE =
42      new Signature("newInstance", BEAN_MAP, new Type[]{ Constants.TYPE_OBJECT });
43    private static final Signature GET_PROPERTY_TYPE =
44      TypeUtils.parseSignature("Class getPropertyType(String)");
45
46    public BeanMapEmitter(ClassVisitor v, String className, Class type, int require) {
47        super(v);
48
49        begin_class(Constants.V1_2, Constants.ACC_PUBLIC, className, BEAN_MAP, null, Constants.SOURCE_FILE);
50        EmitUtils.null_constructor(this);
51        EmitUtils.factory_method(this, NEW_INSTANCE);
52        generateConstructor();
53
54        Map getters = makePropertyMap(ReflectUtils.getBeanGetters(type));
55        Map setters = makePropertyMap(ReflectUtils.getBeanSetters(type));
56        Map allProps = new HashMap();
57        allProps.putAll(getters);
58        allProps.putAll(setters);
59
60        if (require != 0) {
61            for (Iterator it = allProps.keySet().iterator(); it.hasNext();) {
62                String name = (String)it.next();
63                if ((((require & BeanMap.REQUIRE_GETTER) != 0) && !getters.containsKey(name)) ||
64                    (((require & BeanMap.REQUIRE_SETTER) != 0) && !setters.containsKey(name))) {
65                    it.remove();
66                    getters.remove(name);
67                    setters.remove(name);
68                }
69            }
70        }
71        generateGet(type, getters);
72        generatePut(type, setters);
73
74        String[] allNames = getNames(allProps);
75        generateKeySet(allNames);
76        generateGetPropertyType(allProps, allNames);
77        end_class();
78    }
79
80    private Map makePropertyMap(PropertyDescriptor[] props) {
81        Map names = new HashMap();
82        for (int i = 0; i < props.length; i++) {
83            names.put(((PropertyDescriptor)props[i]).getName(), props[i]);
84        }
85        return names;
86    }
87
88    private String[] getNames(Map propertyMap) {
89        return (String[])propertyMap.keySet().toArray(new String[propertyMap.size()]);
90    }
91
92    private void generateConstructor() {
93        CodeEmitter e = begin_method(Constants.ACC_PUBLIC, CSTRUCT_OBJECT, null);
94        e.load_this();
95        e.load_arg(0);
96        e.super_invoke_constructor(CSTRUCT_OBJECT);
97        e.return_value();
98        e.end_method();
99    }
100
101    private void generateGet(Class type, final Map getters) {
102        final CodeEmitter e = begin_method(Constants.ACC_PUBLIC, BEAN_MAP_GET, null);
103        e.load_arg(0);
104        e.checkcast(Type.getType(type));
105        e.load_arg(1);
106        e.checkcast(Constants.TYPE_STRING);
107        EmitUtils.string_switch(e, getNames(getters), Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() {
108            public void processCase(Object key, Label end) {
109                PropertyDescriptor pd = (PropertyDescriptor)getters.get(key);
110                MethodInfo method = ReflectUtils.getMethodInfo(pd.getReadMethod());
111                e.invoke(method);
112                e.box(method.getSignature().getReturnType());
113                e.return_value();
114            }
115            public void processDefault() {
116                e.aconst_null();
117                e.return_value();
118            }
119        });
120        e.end_method();
121    }
122
123    private void generatePut(Class type, final Map setters) {
124        final CodeEmitter e = begin_method(Constants.ACC_PUBLIC, BEAN_MAP_PUT, null);
125        e.load_arg(0);
126        e.checkcast(Type.getType(type));
127        e.load_arg(1);
128        e.checkcast(Constants.TYPE_STRING);
129        EmitUtils.string_switch(e, getNames(setters), Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() {
130            public void processCase(Object key, Label end) {
131                PropertyDescriptor pd = (PropertyDescriptor)setters.get(key);
132                if (pd.getReadMethod() == null) {
133                    e.aconst_null();
134                } else {
135                    MethodInfo read = ReflectUtils.getMethodInfo(pd.getReadMethod());
136                    e.dup();
137                    e.invoke(read);
138                    e.box(read.getSignature().getReturnType());
139                }
140                e.swap(); // move old value behind bean
141                e.load_arg(2); // new value
142                MethodInfo write = ReflectUtils.getMethodInfo(pd.getWriteMethod());
143                e.unbox(write.getSignature().getArgumentTypes()[0]);
144                e.invoke(write);
145                e.return_value();
146            }
147            public void processDefault() {
148                // fall-through
149            }
150        });
151        e.aconst_null();
152        e.return_value();
153        e.end_method();
154    }
155
156    private void generateKeySet(String[] allNames) {
157        // static initializer
158        declare_field(Constants.ACC_STATIC | Constants.ACC_PRIVATE, "keys", FIXED_KEY_SET, null);
159
160        CodeEmitter e = begin_static();
161        e.new_instance(FIXED_KEY_SET);
162        e.dup();
163        EmitUtils.push_array(e, allNames);
164        e.invoke_constructor(FIXED_KEY_SET, CSTRUCT_STRING_ARRAY);
165        e.putfield("keys");
166        e.return_value();
167        e.end_method();
168
169        // keySet
170        e = begin_method(Constants.ACC_PUBLIC, KEY_SET, null);
171        e.load_this();
172        e.getfield("keys");
173        e.return_value();
174        e.end_method();
175    }
176
177    private void generateGetPropertyType(final Map allProps, String[] allNames) {
178        final CodeEmitter e = begin_method(Constants.ACC_PUBLIC, GET_PROPERTY_TYPE, null);
179        e.load_arg(0);
180        EmitUtils.string_switch(e, allNames, Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() {
181            public void processCase(Object key, Label end) {
182                PropertyDescriptor pd = (PropertyDescriptor)allProps.get(key);
183                EmitUtils.load_class(e, Type.getType(pd.getPropertyType()));
184                e.return_value();
185            }
186            public void processDefault() {
187                e.aconst_null();
188                e.return_value();
189            }
190        });
191        e.end_method();
192    }
193}
194