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.PropertyDescriptor;
19import java.lang.reflect.Method;
20
21import org.mockito.asm.ClassVisitor;
22import org.mockito.asm.Type;
23import org.mockito.cglib.core.*;
24
25/**
26 * @author Chris Nokleberg
27 */
28public class ImmutableBean
29{
30    private static final Type ILLEGAL_STATE_EXCEPTION =
31      TypeUtils.parseType("IllegalStateException");
32    private static final Signature CSTRUCT_OBJECT =
33      TypeUtils.parseConstructor("Object");
34    private static final Class[] OBJECT_CLASSES = { Object.class };
35    private static final String FIELD_NAME = "CGLIB$RWBean";
36
37    private ImmutableBean() {
38    }
39
40    public static Object create(Object bean) {
41        Generator gen = new Generator();
42        gen.setBean(bean);
43        return gen.create();
44    }
45
46    public static class Generator extends AbstractClassGenerator {
47        private static final Source SOURCE = new Source(ImmutableBean.class.getName());
48        private Object bean;
49        private Class target;
50
51        public Generator() {
52            super(SOURCE);
53        }
54
55        public void setBean(Object bean) {
56            this.bean = bean;
57            target = bean.getClass();
58        }
59
60        protected ClassLoader getDefaultClassLoader() {
61            return target.getClassLoader();
62        }
63
64        public Object create() {
65            String name = target.getName();
66            setNamePrefix(name);
67            return super.create(name);
68        }
69
70        public void generateClass(ClassVisitor v) {
71            Type targetType = Type.getType(target);
72            ClassEmitter ce = new ClassEmitter(v);
73            ce.begin_class(Constants.V1_2,
74                           Constants.ACC_PUBLIC,
75                           getClassName(),
76                           targetType,
77                           null,
78                           Constants.SOURCE_FILE);
79
80            ce.declare_field(Constants.ACC_FINAL | Constants.ACC_PRIVATE, FIELD_NAME, targetType, null);
81
82            CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, CSTRUCT_OBJECT, null);
83            e.load_this();
84            e.super_invoke_constructor();
85            e.load_this();
86            e.load_arg(0);
87            e.checkcast(targetType);
88            e.putfield(FIELD_NAME);
89            e.return_value();
90            e.end_method();
91
92            PropertyDescriptor[] descriptors = ReflectUtils.getBeanProperties(target);
93            Method[] getters = ReflectUtils.getPropertyMethods(descriptors, true, false);
94            Method[] setters = ReflectUtils.getPropertyMethods(descriptors, false, true);
95
96            for (int i = 0; i < getters.length; i++) {
97                MethodInfo getter = ReflectUtils.getMethodInfo(getters[i]);
98                e = EmitUtils.begin_method(ce, getter, Constants.ACC_PUBLIC);
99                e.load_this();
100                e.getfield(FIELD_NAME);
101                e.invoke(getter);
102                e.return_value();
103                e.end_method();
104            }
105
106            for (int i = 0; i < setters.length; i++) {
107                MethodInfo setter = ReflectUtils.getMethodInfo(setters[i]);
108                e = EmitUtils.begin_method(ce, setter, Constants.ACC_PUBLIC);
109                e.throw_exception(ILLEGAL_STATE_EXCEPTION, "Bean is immutable");
110                e.end_method();
111            }
112
113            ce.end_class();
114        }
115
116        protected Object firstInstance(Class type) {
117            return ReflectUtils.newInstance(type, OBJECT_CLASSES, new Object[]{ bean });
118        }
119
120        // TODO: optimize
121        protected Object nextInstance(Object instance) {
122            return firstInstance(instance.getClass());
123        }
124    }
125}
126