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.proxy;
17
18import java.lang.reflect.Constructor;
19import java.lang.reflect.Modifier;
20import java.util.*;
21
22import org.mockito.asm.ClassVisitor;
23import org.mockito.cglib.core.*;
24
25
26
27/**
28 * <code>Mixin</code> allows
29 * multiple objects to be combined into a single larger object. The
30 * methods in the generated object simply call the original methods in the
31 * underlying "delegate" objects.
32 * @author Chris Nokleberg
33 * @version $Id: Mixin.java,v 1.7 2005/09/27 11:42:27 baliuka Exp $
34 */
35abstract public class Mixin {
36    private static final MixinKey KEY_FACTORY =
37      (MixinKey)KeyFactory.create(MixinKey.class, KeyFactory.CLASS_BY_NAME);
38    private static final Map ROUTE_CACHE = Collections.synchronizedMap(new HashMap());
39
40    public static final int STYLE_INTERFACES = 0;
41    public static final int STYLE_BEANS = 1;
42    public static final int STYLE_EVERYTHING = 2;
43
44    interface MixinKey {
45        public Object newInstance(int style, String[] classes, int[] route);
46    }
47
48    abstract public Mixin newInstance(Object[] delegates);
49
50    /**
51     * Helper method to create an interface mixin. For finer control over the
52     * generated instance, use a new instance of <code>Mixin</code>
53     * instead of this static method.
54     * TODO
55     */
56    public static Mixin create(Object[] delegates) {
57        Generator gen = new Generator();
58        gen.setDelegates(delegates);
59        return gen.create();
60    }
61
62    /**
63     * Helper method to create an interface mixin. For finer control over the
64     * generated instance, use a new instance of <code>Mixin</code>
65     * instead of this static method.
66     * TODO
67     */
68    public static Mixin create(Class[] interfaces, Object[] delegates) {
69        Generator gen = new Generator();
70        gen.setClasses(interfaces);
71        gen.setDelegates(delegates);
72        return gen.create();
73    }
74
75
76    public static Mixin createBean(Object[] beans) {
77
78        return createBean(null, beans);
79
80    }
81    /**
82     * Helper method to create a bean mixin. For finer control over the
83     * generated instance, use a new instance of <code>Mixin</code>
84     * instead of this static method.
85     * TODO
86     */
87    public static Mixin createBean(ClassLoader loader,Object[] beans) {
88        Generator gen = new Generator();
89        gen.setStyle(STYLE_BEANS);
90        gen.setDelegates(beans);
91        gen.setClassLoader(loader);
92        return gen.create();
93    }
94
95    public static class Generator extends AbstractClassGenerator {
96        private static final Source SOURCE = new Source(Mixin.class.getName());
97
98        private Class[] classes;
99        private Object[] delegates;
100        private int style = STYLE_INTERFACES;
101
102        private int[] route;
103
104        public Generator() {
105            super(SOURCE);
106        }
107
108        protected ClassLoader getDefaultClassLoader() {
109            return classes[0].getClassLoader(); // is this right?
110        }
111
112        public void setStyle(int style) {
113            switch (style) {
114            case STYLE_INTERFACES:
115            case STYLE_BEANS:
116            case STYLE_EVERYTHING:
117                this.style = style;
118                break;
119            default:
120                throw new IllegalArgumentException("Unknown mixin style: " + style);
121            }
122        }
123
124        public void setClasses(Class[] classes) {
125            this.classes = classes;
126        }
127
128        public void setDelegates(Object[] delegates) {
129            this.delegates = delegates;
130        }
131
132        public Mixin create() {
133            if (classes == null && delegates == null) {
134                throw new IllegalStateException("Either classes or delegates must be set");
135            }
136            switch (style) {
137            case STYLE_INTERFACES:
138                if (classes == null) {
139                    Route r = route(delegates);
140                    classes = r.classes;
141                    route = r.route;
142                }
143                break;
144            case STYLE_BEANS:
145                // fall-through
146            case STYLE_EVERYTHING:
147                if (classes == null) {
148                    classes = ReflectUtils.getClasses(delegates);
149                } else {
150                    if (delegates != null) {
151                        Class[] temp = ReflectUtils.getClasses(delegates);
152                        if (classes.length != temp.length) {
153                            throw new IllegalStateException("Specified classes are incompatible with delegates");
154                        }
155                        for (int i = 0; i < classes.length; i++) {
156                            if (!classes[i].isAssignableFrom(temp[i])) {
157                                throw new IllegalStateException("Specified class " + classes[i] + " is incompatible with delegate class " + temp[i] + " (index " + i + ")");
158                            }
159                        }
160                    }
161                }
162            }
163            setNamePrefix(classes[ReflectUtils.findPackageProtected(classes)].getName());
164
165            return (Mixin)super.create(KEY_FACTORY.newInstance(style, ReflectUtils.getNames( classes ), route));
166        }
167
168        public void generateClass(ClassVisitor v) {
169            switch (style) {
170            case STYLE_INTERFACES:
171                new MixinEmitter(v, getClassName(), classes, route);
172                break;
173            case STYLE_BEANS:
174                new MixinBeanEmitter(v, getClassName(), classes);
175                break;
176            case STYLE_EVERYTHING:
177                new MixinEverythingEmitter(v, getClassName(), classes);
178                break;
179            }
180        }
181
182        protected Object firstInstance(Class type) {
183            return ((Mixin)ReflectUtils.newInstance(type)).newInstance(delegates);
184        }
185
186        protected Object nextInstance(Object instance) {
187            return ((Mixin)instance).newInstance(delegates);
188        }
189    }
190
191    public static Class[] getClasses(Object[] delegates) {
192        return (Class[])route(delegates).classes.clone();
193    }
194
195//     public static int[] getRoute(Object[] delegates) {
196//         return (int[])route(delegates).route.clone();
197//     }
198
199    private static Route route(Object[] delegates) {
200        Object key = ClassesKey.create(delegates);
201        Route route = (Route)ROUTE_CACHE.get(key);
202        if (route == null) {
203            ROUTE_CACHE.put(key, route = new Route(delegates));
204        }
205        return route;
206    }
207
208    private static class Route
209    {
210        private Class[] classes;
211        private int[] route;
212
213        Route(Object[] delegates) {
214            Map map = new HashMap();
215            ArrayList collect = new ArrayList();
216            for (int i = 0; i < delegates.length; i++) {
217                Class delegate = delegates[i].getClass();
218                collect.clear();
219                ReflectUtils.addAllInterfaces(delegate, collect);
220                for (Iterator it = collect.iterator(); it.hasNext();) {
221                    Class iface = (Class)it.next();
222                    if (!map.containsKey(iface)) {
223                        map.put(iface, new Integer(i));
224                    }
225                }
226            }
227            classes = new Class[map.size()];
228            route = new int[map.size()];
229            int index = 0;
230            for (Iterator it = map.keySet().iterator(); it.hasNext();) {
231                Class key = (Class)it.next();
232                classes[index] = key;
233                route[index] = ((Integer)map.get(key)).intValue();
234                index++;
235            }
236        }
237    }
238}
239