001// Copyright 2004, 2005 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007//     http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.hivemind.service.impl;
016
017import java.lang.reflect.Modifier;
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.Iterator;
021import java.util.List;
022import java.util.Map;
023
024import javassist.CannotCompileException;
025import javassist.CtClass;
026import javassist.CtConstructor;
027import javassist.CtField;
028import javassist.CtMethod;
029import javassist.NotFoundException;
030
031import org.apache.hivemind.ApplicationRuntimeException;
032import org.apache.hivemind.service.ClassFab;
033import org.apache.hivemind.service.MethodFab;
034import org.apache.hivemind.service.MethodSignature;
035
036/**
037 * Implementation of {@link org.apache.hivemind.service.ClassFab}. Hides, as much as possible, the
038 * underlying library (Javassist).
039 * 
040 * @author Howard Lewis Ship
041 */
042public class ClassFabImpl extends AbstractFab implements ClassFab
043{
044    /**
045     * Stores information about a constructor; used by toString().
046     * 
047     * @since 1.1
048     */
049
050    private class AddedConstructor
051    {
052        private Class[] _parameterTypes;
053
054        private Class[] _exceptionTypes;
055
056        private String _body;
057
058        AddedConstructor(Class[] parameterTypes, Class[] exceptionTypes, String body)
059        {
060            _parameterTypes = parameterTypes;
061            _exceptionTypes = exceptionTypes;
062            _body = body;
063        }
064
065        public String toString()
066        {
067            StringBuffer buffer = new StringBuffer();
068
069            buffer.append("public ");
070            buffer.append(getCtClass().getName());
071
072            buffer.append("(");
073
074            int count = size(_parameterTypes);
075            for (int i = 0; i < count; i++)
076            {
077                if (i > 0)
078                    buffer.append(", ");
079
080                buffer.append(_parameterTypes[i].getName());
081
082                buffer.append(" $");
083                buffer.append(i + 1);
084            }
085
086            buffer.append(")");
087
088            count = size(_exceptionTypes);
089            for (int i = 0; i < count; i++)
090            {
091                if (i == 0)
092                    buffer.append("\n  throws ");
093                else
094                    buffer.append(", ");
095
096                buffer.append(_exceptionTypes[i].getName());
097            }
098
099            buffer.append("\n");
100            buffer.append(_body);
101
102            buffer.append("\n");
103
104            return buffer.toString();
105        }
106
107        private int size(Object[] array)
108        {
109            return array == null ? 0 : array.length;
110        }
111    }
112
113    /**
114     * Map of {@link MethodFab}keyed on {@link MethodSignature}.
115     */
116    private Map _methods = new HashMap();
117
118    /**
119     * List of {@link AddedConstructor}.
120     * 
121     * @since 1.1
122     */
123
124    private List _constructors = new ArrayList();
125
126    public ClassFabImpl(CtClassSource source, CtClass ctClass)
127    {
128        super(source, ctClass);
129    }
130
131    /**
132     * Returns a representation of the fabricated class, including inheritance, fields,
133     * constructors, methods and method bodies.
134     * 
135     * @since 1.1
136     */
137    public String toString()
138    {
139        StringBuffer buffer = new StringBuffer("ClassFab[\n");
140
141        try
142        {
143            buildClassAndInheritance(buffer);
144
145            buildFields(buffer);
146
147            buildConstructors(buffer);
148
149            buildMethods(buffer);
150
151        }
152        catch (Exception ex)
153        {
154            buffer.append(" *** ");
155            buffer.append(ex);
156        }
157
158        buffer.append("\n]");
159
160        return buffer.toString();
161    }
162
163    /** @since 1.1 */
164    private void buildMethods(StringBuffer buffer)
165    {
166        Iterator i = _methods.values().iterator();
167        while (i.hasNext())
168        {
169
170            MethodFab mf = (MethodFab) i.next();
171
172            buffer.append("\n");
173            buffer.append(mf);
174            buffer.append("\n");
175        }
176    }
177
178    /** @since 1.1 */
179    private void buildConstructors(StringBuffer buffer)
180    {
181        Iterator i = _constructors.iterator();
182
183        while (i.hasNext())
184        {
185            buffer.append("\n");
186            buffer.append(i.next());
187        }
188    }
189
190    /** @since 1.1 */
191    private void buildFields(StringBuffer buffer) throws NotFoundException
192    {
193        CtField fields[] = getCtClass().getDeclaredFields();
194
195        for (int i = 0; i < fields.length; i++)
196        {
197            buffer.append("\n");
198            buffer.append(modifiers(fields[i].getModifiers()));
199            buffer.append(" ");
200            buffer.append(fields[i].getType().getName());
201            buffer.append(" ");
202            buffer.append(fields[i].getName());
203            buffer.append(";\n");
204        }
205    }
206
207    /** @since 1.1 */
208    private void buildClassAndInheritance(StringBuffer buffer) throws NotFoundException
209    {
210        buffer.append(modifiers(getCtClass().getModifiers()));
211        buffer.append(" class ");
212        buffer.append(getCtClass().getName());
213        buffer.append(" extends ");
214        buffer.append(getCtClass().getSuperclass().getName());
215        buffer.append("\n");
216
217        CtClass[] interfaces = getCtClass().getInterfaces();
218
219        if (interfaces.length > 0)
220        {
221            buffer.append("  implements ");
222
223            for (int i = 0; i < interfaces.length; i++)
224            {
225                if (i > 0)
226                    buffer.append(", ");
227
228                buffer.append(interfaces[i].getName());
229            }
230
231            buffer.append("\n");
232        }
233    }
234
235    private String modifiers(int modifiers)
236    {
237        return Modifier.toString(modifiers);
238    }
239
240    /**
241     * Returns the name of the class fabricated by this instance.
242     */
243    String getName()
244    {
245        return getCtClass().getName();
246    }
247
248    public void addField(String name, Class type)
249    {
250        CtClass ctType = convertClass(type);
251
252        try
253        {
254            CtField field = new CtField(ctType, name, getCtClass());
255            field.setModifiers(Modifier.PRIVATE);
256
257            getCtClass().addField(field);
258        }
259        catch (CannotCompileException ex)
260        {
261            throw new ApplicationRuntimeException(ServiceMessages.unableToAddField(
262                    name,
263                    getCtClass(),
264                    ex), ex);
265        }
266    }
267
268    public boolean containsMethod( MethodSignature ms )
269    {
270        return _methods.get( ms ) != null;
271    }
272    
273    public MethodFab addMethod(int modifiers, MethodSignature ms, String body)
274    {
275        if (_methods.get(ms) != null)
276            throw new ApplicationRuntimeException(ServiceMessages.duplicateMethodInClass(ms, this));
277
278        CtClass ctReturnType = convertClass(ms.getReturnType());
279
280        CtClass[] ctParameters = convertClasses(ms.getParameterTypes());
281        CtClass[] ctExceptions = convertClasses(ms.getExceptionTypes());
282
283        CtMethod method = new CtMethod(ctReturnType, ms.getName(), ctParameters, getCtClass());
284
285        try
286        {
287            method.setModifiers(modifiers);
288            method.setBody(body);
289            method.setExceptionTypes(ctExceptions);
290
291            getCtClass().addMethod(method);
292        }
293        catch (Exception ex)
294        {
295            throw new ApplicationRuntimeException(ServiceMessages.unableToAddMethod(
296                    ms,
297                    getCtClass(),
298                    ex), ex);
299        }
300
301        // Return a MethodFab so the caller can add catches.
302
303        MethodFab result = new MethodFabImpl(getSource(), ms, method, body);
304
305        _methods.put(ms, result);
306
307        return result;
308    }
309
310    public MethodFab getMethodFab(MethodSignature ms)
311    {
312        return (MethodFab) _methods.get(ms);
313    }
314
315    public void addConstructor(Class[] parameterTypes, Class[] exceptions, String body)
316    {
317        CtClass[] ctParameters = convertClasses(parameterTypes);
318        CtClass[] ctExceptions = convertClasses(exceptions);
319
320        try
321        {
322            CtConstructor constructor = new CtConstructor(ctParameters, getCtClass());
323            constructor.setExceptionTypes(ctExceptions);
324            constructor.setBody(body);
325
326            getCtClass().addConstructor(constructor);
327
328            _constructors.add(new AddedConstructor(parameterTypes, exceptions, body));
329        }
330        catch (Exception ex)
331        {
332            throw new ApplicationRuntimeException(ServiceMessages.unableToAddConstructor(
333                    getCtClass(),
334                    ex), ex);
335        }
336    }
337
338}