InterceptFieldTransformer.java revision 674060f01e9090cd21b3c5656cc3204912ad17a6
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 org.mockito.asm.Attribute;
19import org.mockito.asm.ClassVisitor;
20import org.mockito.asm.Label;
21import org.mockito.asm.MethodAdapter;
22import org.mockito.asm.MethodVisitor;
23import org.mockito.asm.Type;
24import org.mockito.cglib.core.*;
25import org.mockito.cglib.transform.*;
26
27/**
28 * @author Juozas Baliuka, Chris Nokleberg
29 */
30public class InterceptFieldTransformer extends ClassEmitterTransformer {
31    private static final String CALLBACK_FIELD = "$CGLIB_READ_WRITE_CALLBACK";
32    private static final Type CALLBACK =
33      TypeUtils.parseType("org.mockito.cglib.transform.impl.InterceptFieldCallback");
34    private static final Type ENABLED =
35      TypeUtils.parseType("org.mockito.cglib.transform.impl.InterceptFieldEnabled");
36    private static final Signature ENABLED_SET =
37      new Signature("setInterceptFieldCallback", Type.VOID_TYPE, new Type[]{ CALLBACK });
38    private static final Signature ENABLED_GET =
39      new Signature("getInterceptFieldCallback", CALLBACK, new Type[0]);
40
41    private InterceptFieldFilter filter;
42
43    public InterceptFieldTransformer(InterceptFieldFilter filter) {
44        this.filter = filter;
45    }
46
47    public void begin_class(int version, int access, String className, Type superType, Type[] interfaces, String sourceFile) {
48        if (!TypeUtils.isInterface(access)) {
49            super.begin_class(version, access, className, superType, TypeUtils.add(interfaces, ENABLED), sourceFile);
50
51            super.declare_field(Constants.ACC_PRIVATE | Constants.ACC_TRANSIENT,
52                                CALLBACK_FIELD,
53                                CALLBACK,
54                                null);
55
56            CodeEmitter e;
57            e = super.begin_method(Constants.ACC_PUBLIC, ENABLED_GET, null);
58            e.load_this();
59            e.getfield(CALLBACK_FIELD);
60            e.return_value();
61            e.end_method();
62
63            e = super.begin_method(Constants.ACC_PUBLIC, ENABLED_SET, null);
64            e.load_this();
65            e.load_arg(0);
66            e.putfield(CALLBACK_FIELD);
67            e.return_value();
68            e.end_method();
69        } else {
70            super.begin_class(version, access, className, superType, interfaces, sourceFile);
71        }
72    }
73
74    public void declare_field(int access, String name, Type type, Object value) {
75        super.declare_field(access, name, type, value);
76        if (!TypeUtils.isStatic(access)) {
77            if (filter.acceptRead(getClassType(), name)) {
78                addReadMethod(name, type);
79            }
80            if (filter.acceptWrite(getClassType(), name)) {
81                addWriteMethod(name, type);
82            }
83        }
84    }
85
86    private void addReadMethod(String name, Type type) {
87        CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC,
88                                           readMethodSig(name, type.getDescriptor()),
89                                           null);
90        e.load_this();
91        e.getfield(name);
92        e.load_this();
93        e.invoke_interface(ENABLED,ENABLED_GET);
94        Label intercept = e.make_label();
95        e.ifnonnull(intercept);
96        e.return_value();
97
98        e.mark(intercept);
99        Local result = e.make_local(type);
100        e.store_local(result);
101        e.load_this();
102        e.invoke_interface(ENABLED,ENABLED_GET);
103        e.load_this();
104        e.push(name);
105        e.load_local(result);
106        e.invoke_interface(CALLBACK, readCallbackSig(type));
107        if (!TypeUtils.isPrimitive(type)) {
108            e.checkcast(type);
109        }
110        e.return_value();
111        e.end_method();
112    }
113
114    private void addWriteMethod(String name, Type type) {
115        CodeEmitter e = super.begin_method(Constants.ACC_PUBLIC,
116                                           writeMethodSig(name, type.getDescriptor()),
117                                           null);
118        e.load_this();
119        e.dup();
120        e.invoke_interface(ENABLED,ENABLED_GET);
121        Label skip = e.make_label();
122        e.ifnull(skip);
123
124        e.load_this();
125        e.invoke_interface(ENABLED,ENABLED_GET);
126        e.load_this();
127        e.push(name);
128        e.load_this();
129        e.getfield(name);
130        e.load_arg(0);
131        e.invoke_interface(CALLBACK, writeCallbackSig(type));
132        if (!TypeUtils.isPrimitive(type)) {
133            e.checkcast(type);
134        }
135        Label go = e.make_label();
136        e.goTo(go);
137        e.mark(skip);
138        e.load_arg(0);
139        e.mark(go);
140        e.putfield(name);
141        e.return_value();
142        e.end_method();
143    }
144
145    public CodeEmitter begin_method(int access, Signature sig, Type[] exceptions) {
146        return new CodeEmitter(super.begin_method(access, sig, exceptions)) {
147            public void visitFieldInsn(int opcode, String owner, String name, String desc) {
148                Type towner = TypeUtils.fromInternalName(owner);
149                switch (opcode) {
150                case Constants.GETFIELD:
151                    if (filter.acceptRead(towner, name)) {
152                        helper(towner, readMethodSig(name, desc));
153                        return;
154                    }
155                    break;
156                case Constants.PUTFIELD:
157                    if (filter.acceptWrite(towner, name)) {
158                        helper(towner, writeMethodSig(name, desc));
159                        return;
160                    }
161                    break;
162                }
163                super.visitFieldInsn(opcode, owner, name, desc);
164            }
165
166            private void helper(Type owner, Signature sig) {
167                invoke_virtual(owner, sig);
168            }
169        };
170    }
171
172    private static Signature readMethodSig(String name, String desc) {
173        return new Signature("$cglib_read_" + name, "()" + desc);
174    }
175
176    private static Signature writeMethodSig(String name, String desc) {
177        return new Signature("$cglib_write_" + name, "(" + desc + ")V");
178    }
179
180    private static Signature readCallbackSig(Type type) {
181        Type remap = remap(type);
182        return new Signature("read" + callbackName(remap),
183                             remap,
184                             new Type[]{ Constants.TYPE_OBJECT,
185                                         Constants.TYPE_STRING,
186                                         remap });
187    }
188
189    private static Signature writeCallbackSig(Type type) {
190        Type remap = remap(type);
191        return new Signature("write" + callbackName(remap),
192                             remap,
193                             new Type[]{ Constants.TYPE_OBJECT,
194                                         Constants.TYPE_STRING,
195                                         remap,
196                                         remap });
197    }
198
199    private static Type remap(Type type) {
200        switch (type.getSort()) {
201        case Type.OBJECT:
202        case Type.ARRAY:
203            return Constants.TYPE_OBJECT;
204        default:
205            return type;
206        }
207    }
208
209    private static String callbackName(Type type) {
210        return (type == Constants.TYPE_OBJECT) ?
211            "Object" :
212            TypeUtils.upperFirst(TypeUtils.getClassName(type));
213    }
214}
215