1/*
2 * Copyright 2003 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.transform.impl;
17
18import java.util.*;
19
20import org.mockito.asm.Attribute;
21import org.mockito.asm.Label;
22import org.mockito.asm.Type;
23import org.mockito.cglib.core.*;
24import org.mockito.cglib.transform.*;
25
26public class FieldProviderTransformer extends ClassEmitterTransformer {
27
28    private static final String FIELD_NAMES = "CGLIB$FIELD_NAMES";
29    private static final String FIELD_TYPES = "CGLIB$FIELD_TYPES";
30
31    private static final Type FIELD_PROVIDER =
32      TypeUtils.parseType("org.mockito.cglib.transform.impl.FieldProvider");
33    private static final Type ILLEGAL_ARGUMENT_EXCEPTION =
34      TypeUtils.parseType("IllegalArgumentException");
35    private static final Signature PROVIDER_GET =
36      TypeUtils.parseSignature("Object getField(String)");
37    private static final Signature PROVIDER_SET =
38      TypeUtils.parseSignature("void setField(String, Object)");
39    private static final Signature PROVIDER_SET_BY_INDEX =
40      TypeUtils.parseSignature("void setField(int, Object)");
41    private static final Signature PROVIDER_GET_BY_INDEX =
42      TypeUtils.parseSignature("Object getField(int)");
43    private static final Signature PROVIDER_GET_TYPES =
44      TypeUtils.parseSignature("Class[] getFieldTypes()");
45    private static final Signature PROVIDER_GET_NAMES =
46      TypeUtils.parseSignature("String[] getFieldNames()");
47
48    private int access;
49    private Map fields;
50
51    public void begin_class(int version, int access, String className, Type superType, Type[] interfaces, String sourceFile) {
52        if (!TypeUtils.isAbstract(access)) {
53            interfaces = TypeUtils.add(interfaces, FIELD_PROVIDER);
54        }
55        this.access = access;
56        fields = new HashMap();
57        super.begin_class(version, access, className, superType, interfaces, sourceFile);
58    }
59
60    public void declare_field(int access, String name, Type type, Object value) {
61        super.declare_field(access, name, type, value);
62
63        if (!TypeUtils.isStatic(access)) {
64            fields.put(name, type);
65        }
66    }
67
68    public void end_class() {
69        if (!TypeUtils.isInterface(access)) {
70            try {
71                generate();
72            } catch (RuntimeException e) {
73                throw e;
74            } catch (Exception e) {
75                throw new CodeGenerationException(e);
76            }
77        }
78        super.end_class();
79    }
80
81    private void generate() throws Exception {
82        final String[] names = (String[])fields.keySet().toArray(new String[fields.size()]);
83
84        int indexes[] = new int[names.length];
85        for (int i = 0; i < indexes.length; i++) {
86            indexes[i] = i;
87        }
88
89        super.declare_field(Constants.PRIVATE_FINAL_STATIC, FIELD_NAMES, Constants.TYPE_STRING_ARRAY, null);
90        super.declare_field(Constants.PRIVATE_FINAL_STATIC, FIELD_TYPES, Constants.TYPE_CLASS_ARRAY, null);
91
92        // use separate methods here because each process switch inner class needs a final CodeEmitter
93        initFieldProvider(names);
94        getNames();
95        getTypes();
96        getField(names);
97        setField(names);
98        setByIndex(names, indexes);
99        getByIndex(names, indexes);
100    }
101
102    private void initFieldProvider(String[] names) {
103        CodeEmitter e = getStaticHook();
104        EmitUtils.push_object(e, names);
105        e.putstatic(getClassType(), FIELD_NAMES, Constants.TYPE_STRING_ARRAY);
106
107        e.push(names.length);
108        e.newarray(Constants.TYPE_CLASS);
109        e.dup();
110        for(int i = 0; i < names.length; i++ ){
111            e.dup();
112            e.push(i);
113            Type type = (Type)fields.get(names[i]);
114            EmitUtils.load_class(e, type);
115            e.aastore();
116        }
117        e.putstatic(getClassType(), FIELD_TYPES, Constants.TYPE_CLASS_ARRAY);
118    }
119
120    private void getNames() {
121        CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, PROVIDER_GET_NAMES, null);
122        e.getstatic(getClassType(), FIELD_NAMES, Constants.TYPE_STRING_ARRAY);
123        e.return_value();
124        e.end_method();
125    }
126
127    private void getTypes() {
128        CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, PROVIDER_GET_TYPES, null);
129        e.getstatic(getClassType(), FIELD_TYPES, Constants.TYPE_CLASS_ARRAY);
130        e.return_value();
131        e.end_method();
132    }
133
134    private void setByIndex(final String[] names, final int[] indexes) throws Exception {
135        final CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, PROVIDER_SET_BY_INDEX, null);
136        e.load_this();
137        e.load_arg(1);
138        e.load_arg(0);
139        e.process_switch(indexes, new ProcessSwitchCallback() {
140            public void processCase(int key, Label end) throws Exception {
141                Type type = (Type)fields.get(names[key]);
142                e.unbox(type);
143                e.putfield(names[key]);
144                e.return_value();
145            }
146            public void processDefault() throws Exception {
147                e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Unknown field index");
148            }
149        });
150        e.end_method();
151    }
152
153    private void getByIndex(final String[] names, final int[] indexes) throws Exception {
154        final CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC, PROVIDER_GET_BY_INDEX, null);
155        e.load_this();
156        e.load_arg(0);
157        e.process_switch(indexes, new ProcessSwitchCallback() {
158            public void processCase(int key, Label end) throws Exception {
159                Type type = (Type)fields.get(names[key]);
160                e.getfield(names[key]);
161                e.box(type);
162                e.return_value();
163            }
164            public void processDefault() throws Exception {
165                e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Unknown field index");
166            }
167        });
168        e.end_method();
169    }
170
171    // TODO: if this is used to enhance class files SWITCH_STYLE_TRIE should be used
172    // to avoid JVM hashcode implementation incompatibilities
173    private void getField(String[] names) throws Exception {
174        final CodeEmitter e = begin_method(Constants.ACC_PUBLIC, PROVIDER_GET, null);
175        e.load_this();
176        e.load_arg(0);
177        EmitUtils.string_switch(e, names, Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() {
178            public void processCase(Object key, Label end) {
179                Type type = (Type)fields.get(key);
180                e.getfield((String)key);
181                e.box(type);
182                e.return_value();
183            }
184            public void processDefault() {
185                e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Unknown field name");
186            }
187        });
188        e.end_method();
189    }
190
191    private void setField(String[] names) throws Exception {
192        final CodeEmitter e = begin_method(Constants.ACC_PUBLIC, PROVIDER_SET, null);
193        e.load_this();
194        e.load_arg(1);
195        e.load_arg(0);
196        EmitUtils.string_switch(e, names, Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() {
197            public void processCase(Object key, Label end) {
198                Type type = (Type)fields.get(key);
199                e.unbox(type);
200                e.putfield((String)key);
201                e.return_value();
202            }
203            public void processDefault() {
204                e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Unknown field name");
205            }
206        });
207        e.end_method();
208    }
209}
210