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.core;
17
18import java.io.*;
19import java.util.*;
20
21import org.mockito.asm.*;
22
23/**
24 * @author Juozas Baliuka, Chris Nokleberg
25 */
26public class ClassEmitter extends ClassAdapter {
27    private ClassInfo classInfo;
28    private Map fieldInfo;
29
30    private static int hookCounter;
31    private MethodVisitor rawStaticInit;
32    private CodeEmitter staticInit;
33    private CodeEmitter staticHook;
34    private Signature staticHookSig;
35
36    public ClassEmitter(ClassVisitor cv) {
37        super(null);
38        setTarget(cv);
39    }
40
41    public ClassEmitter() {
42        super(null);
43    }
44
45    public void setTarget(ClassVisitor cv) {
46        this.cv = cv;
47        fieldInfo = new HashMap();
48
49        // just to be safe
50        staticInit = staticHook = null;
51        staticHookSig = null;
52    }
53
54    synchronized private static int getNextHook() {
55        return ++hookCounter;
56    }
57
58    public ClassInfo getClassInfo() {
59        return classInfo;
60    }
61
62    public void begin_class(int version, final int access, String className, final Type superType, final Type[] interfaces, String source) {
63        final Type classType = Type.getType("L" + className.replace('.', '/') + ";");
64        classInfo = new ClassInfo() {
65            public Type getType() {
66                return classType;
67            }
68            public Type getSuperType() {
69                return (superType != null) ? superType : Constants.TYPE_OBJECT;
70            }
71            public Type[] getInterfaces() {
72                return interfaces;
73            }
74            public int getModifiers() {
75                return access;
76            }
77        };
78        cv.visit(version,
79                 access,
80                 classInfo.getType().getInternalName(),
81                 null,
82                 classInfo.getSuperType().getInternalName(),
83                 TypeUtils.toInternalNames(interfaces));
84        if (source != null)
85            cv.visitSource(source, null);
86        init();
87    }
88
89    public CodeEmitter getStaticHook() {
90         if (TypeUtils.isInterface(getAccess())) {
91             throw new IllegalStateException("static hook is invalid for this class");
92         }
93         if (staticHook == null) {
94             staticHookSig = new Signature("CGLIB$STATICHOOK" + getNextHook(), "()V");
95             staticHook = begin_method(Constants.ACC_STATIC,
96                                       staticHookSig,
97                                       null);
98             if (staticInit != null) {
99                 staticInit.invoke_static_this(staticHookSig);
100             }
101         }
102         return staticHook;
103    }
104
105    protected void init() {
106    }
107
108    public int getAccess() {
109        return classInfo.getModifiers();
110    }
111
112    public Type getClassType() {
113        return classInfo.getType();
114    }
115
116    public Type getSuperType() {
117        return classInfo.getSuperType();
118    }
119
120    public void end_class() {
121        if (staticHook != null && staticInit == null) {
122            // force creation of static init
123            begin_static();
124        }
125        if (staticInit != null) {
126            staticHook.return_value();
127            staticHook.end_method();
128            rawStaticInit.visitInsn(Constants.RETURN);
129            rawStaticInit.visitMaxs(0, 0);
130            staticInit = staticHook = null;
131            staticHookSig = null;
132        }
133        cv.visitEnd();
134    }
135
136    public CodeEmitter begin_method(int access, Signature sig, Type[] exceptions) {
137        if (classInfo == null)
138            throw new IllegalStateException("classInfo is null! " + this);
139        MethodVisitor v = cv.visitMethod(access,
140                                         sig.getName(),
141                                         sig.getDescriptor(),
142                                         null,
143                                         TypeUtils.toInternalNames(exceptions));
144        if (sig.equals(Constants.SIG_STATIC) && !TypeUtils.isInterface(getAccess())) {
145            rawStaticInit = v;
146            MethodVisitor wrapped = new MethodAdapter(v) {
147                public void visitMaxs(int maxStack, int maxLocals) {
148                    // ignore
149                }
150                public void visitInsn(int insn) {
151                    if (insn != Constants.RETURN) {
152                        super.visitInsn(insn);
153                    }
154                }
155            };
156            staticInit = new CodeEmitter(this, wrapped, access, sig, exceptions);
157            if (staticHook == null) {
158                // force static hook creation
159                getStaticHook();
160            } else {
161                staticInit.invoke_static_this(staticHookSig);
162            }
163            return staticInit;
164        } else if (sig.equals(staticHookSig)) {
165            return new CodeEmitter(this, v, access, sig, exceptions) {
166                public boolean isStaticHook() {
167                    return true;
168                }
169            };
170        } else {
171            return new CodeEmitter(this, v, access, sig, exceptions);
172        }
173    }
174
175    public CodeEmitter begin_static() {
176        return begin_method(Constants.ACC_STATIC, Constants.SIG_STATIC, null);
177    }
178
179    public void declare_field(int access, String name, Type type, Object value) {
180        FieldInfo existing = (FieldInfo)fieldInfo.get(name);
181        FieldInfo info = new FieldInfo(access, name, type, value);
182        if (existing != null) {
183            if (!info.equals(existing)) {
184                throw new IllegalArgumentException("Field \"" + name + "\" has been declared differently");
185            }
186        } else {
187            fieldInfo.put(name, info);
188            cv.visitField(access, name, type.getDescriptor(), null, value);
189        }
190    }
191
192    // TODO: make public?
193    boolean isFieldDeclared(String name) {
194        return fieldInfo.get(name) != null;
195    }
196
197    FieldInfo getFieldInfo(String name) {
198        FieldInfo field = (FieldInfo)fieldInfo.get(name);
199        if (field == null) {
200            throw new IllegalArgumentException("Field " + name + " is not declared in " + getClassType().getClassName());
201        }
202        return field;
203    }
204
205    static class FieldInfo {
206        int access;
207        String name;
208        Type type;
209        Object value;
210
211        public FieldInfo(int access, String name, Type type, Object value) {
212            this.access = access;
213            this.name = name;
214            this.type = type;
215            this.value = value;
216        }
217
218        public boolean equals(Object o) {
219            if (o == null)
220                return false;
221            if (!(o instanceof FieldInfo))
222                return false;
223            FieldInfo other = (FieldInfo)o;
224            if (access != other.access ||
225                !name.equals(other.name) ||
226                !type.equals(other.type)) {
227                return false;
228            }
229            if ((value == null) ^ (other.value == null))
230                return false;
231            if (value != null && !value.equals(other.value))
232                return false;
233            return true;
234        }
235
236        public int hashCode() {
237            return access ^ name.hashCode() ^ type.hashCode() ^ ((value == null) ? 0 : value.hashCode());
238        }
239    }
240
241    public void visit(int version,
242                      int access,
243                      String name,
244                      String signature,
245                      String superName,
246                      String[] interfaces) {
247        begin_class(version,
248                    access,
249                    name.replace('/', '.'),
250                    TypeUtils.fromInternalName(superName),
251                    TypeUtils.fromInternalNames(interfaces),
252                    null); // TODO
253    }
254
255    public void visitEnd() {
256        end_class();
257    }
258
259    public FieldVisitor visitField(int access,
260                                   String name,
261                                   String desc,
262                                   String signature,
263                                   Object value) {
264        declare_field(access, name, Type.getType(desc), value);
265        return null; // TODO
266    }
267
268    public MethodVisitor visitMethod(int access,
269                                     String name,
270                                     String desc,
271                                     String signature,
272                                     String[] exceptions) {
273        return begin_method(access,
274                            new Signature(name, desc),
275                            TypeUtils.fromInternalNames(exceptions));
276    }
277}
278