001    // Copyright 2007 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    package org.apache.tapestry.enhance;
015    
016    import java.lang.reflect.Modifier;
017    import java.util.ArrayList;
018    import java.util.HashMap;
019    import java.util.Iterator;
020    import java.util.List;
021    import java.util.Map;
022    
023    import javassist.CannotCompileException;
024    import javassist.CtClass;
025    import javassist.CtConstructor;
026    import javassist.CtField;
027    import javassist.CtMethod;
028    import javassist.NotFoundException;
029    
030    import org.apache.hivemind.ApplicationRuntimeException;
031    import org.apache.hivemind.service.ClassFab;
032    import org.apache.hivemind.service.MethodFab;
033    import org.apache.hivemind.service.MethodSignature;
034    
035    /**
036     * Implementation replacement for hivemind {@link ClassFab} utiltity to get around some javassist
037     * incompatibilties found with the latest 3.4 version of javassist.
038     * 
039     * @author jkuhnert
040     */
041    public class ClassFabImpl extends AbstractFab implements ClassFab
042    {
043        /**
044         * Stores information about a constructor; used by toString().
045         * 
046         * @since 1.1
047         */
048    
049        private class AddedConstructor
050        {
051            private Class[] _parameterTypes;
052    
053            private Class[] _exceptionTypes;
054    
055            private String _body;
056    
057            AddedConstructor(Class[] parameterTypes, Class[] exceptionTypes, String body)
058            {
059                _parameterTypes = parameterTypes;
060                _exceptionTypes = exceptionTypes;
061                _body = body;
062            }
063    
064            public String toString()
065            {
066                StringBuffer buffer = new StringBuffer();
067    
068                buffer.append("public ");
069                buffer.append(getCtClass().getName());
070    
071                buffer.append("(");
072    
073                int count = size(_parameterTypes);
074                for(int i = 0; i < count; i++) {
075                    if (i > 0) buffer.append(", ");
076    
077                    buffer.append(_parameterTypes[i].getName());
078    
079                    buffer.append(" $");
080                    buffer.append(i + 1);
081                }
082    
083                buffer.append(")");
084    
085                count = size(_exceptionTypes);
086                for(int i = 0; i < count; i++) {
087                    if (i == 0)
088                        buffer.append("\n  throws ");
089                    else buffer.append(", ");
090    
091                    buffer.append(_exceptionTypes[i].getName());
092                }
093    
094                buffer.append("\n");
095                buffer.append(_body);
096    
097                buffer.append("\n");
098    
099                return buffer.toString();
100            }
101    
102            private int size(Object[] array)
103            {
104                return array == null ? 0 : array.length;
105            }
106        }
107    
108        /**
109         * Map of {@link MethodFab}keyed on {@link MethodSignature}.
110         */
111        private Map _methods = new HashMap();
112    
113        /**
114         * List of {@link AddedConstructor}.
115         * 
116         * @since 1.1
117         */
118    
119        private List _constructors = new ArrayList();
120    
121        public ClassFabImpl(CtClassSource source, CtClass ctClass)
122        {
123            super(source, ctClass);
124        }
125    
126        /**
127         * Returns a representation of the fabricated class, including inheritance, fields,
128         * constructors, methods and method bodies.
129         * 
130         * @since 1.1
131         */
132        public String toString()
133        {
134            StringBuffer buffer = new StringBuffer("ClassFab[\n");
135    
136            try {
137                buildClassAndInheritance(buffer);
138    
139                buildFields(buffer);
140    
141                buildConstructors(buffer);
142    
143                buildMethods(buffer);
144    
145            } catch (Exception ex) {
146                buffer.append(" *** ");
147                buffer.append(ex);
148            }
149    
150            buffer.append("\n]");
151    
152            return buffer.toString();
153        }
154    
155        /** @since 1.1 */
156        private void buildMethods(StringBuffer buffer)
157        {
158            Iterator i = _methods.values().iterator();
159            while(i.hasNext()) {
160    
161                MethodFab mf = (MethodFab) i.next();
162    
163                buffer.append("\n");
164                buffer.append(mf);
165                buffer.append("\n");
166            }
167        }
168    
169        /** @since 1.1 */
170        private void buildConstructors(StringBuffer buffer)
171        {
172            Iterator i = _constructors.iterator();
173    
174            while(i.hasNext()) {
175                buffer.append("\n");
176                buffer.append(i.next());
177            }
178        }
179    
180        /** @since 1.1 */
181        private void buildFields(StringBuffer buffer)
182            throws NotFoundException
183        {
184            CtField[] fields = getCtClass().getDeclaredFields();
185    
186            for(int i = 0; i < fields.length; i++) {
187                buffer.append("\n");
188                buffer.append(modifiers(fields[i].getModifiers()));
189                buffer.append(" ");
190                buffer.append(fields[i].getType().getName());
191                buffer.append(" ");
192                buffer.append(fields[i].getName());
193                buffer.append(";\n");
194            }
195        }
196    
197        /** @since 1.1 */
198        private void buildClassAndInheritance(StringBuffer buffer)
199            throws NotFoundException
200        {
201            buffer.append(modifiers(getCtClass().getModifiers()));
202            buffer.append(" class ");
203            buffer.append(getCtClass().getName());
204            buffer.append(" extends ");
205            buffer.append(getCtClass().getSuperclass().getName());
206            buffer.append("\n");
207    
208            CtClass[] interfaces = getCtClass().getInterfaces();
209    
210            if (interfaces.length > 0) {
211                buffer.append("  implements ");
212    
213                for(int i = 0; i < interfaces.length; i++) {
214                    if (i > 0) buffer.append(", ");
215    
216                    buffer.append(interfaces[i].getName());
217                }
218    
219                buffer.append("\n");
220            }
221        }
222    
223        private String modifiers(int modifiers)
224        {
225            return Modifier.toString(modifiers);
226        }
227    
228        /**
229         * Returns the name of the class fabricated by this instance.
230         */
231        String getName()
232        {
233            return getCtClass().getName();
234        }
235    
236        public void addField(String name, Class type)
237        {
238            CtClass ctType = convertClass(type);
239    
240            try {
241                CtField field = new CtField(ctType, name, getCtClass());
242                field.setModifiers(Modifier.PRIVATE);
243    
244                getCtClass().addField(field);
245            } catch (CannotCompileException ex) {
246                throw new ApplicationRuntimeException(EnhanceMessages.unableToAddField(name, getCtClass(), ex), ex);
247            }
248        }
249    
250        public boolean containsMethod(MethodSignature ms)
251        {
252            return _methods.get(ms) != null;
253        }
254    
255        public MethodFab addMethod(int modifiers, MethodSignature ms, String body)
256        {
257            if (_methods.get(ms) != null)
258                throw new ApplicationRuntimeException(EnhanceMessages.duplicateMethodInClass(ms, this));
259            
260            if (body.indexOf("isWrapperFor") > 0 || body.indexOf("unwrap") > 0)
261                return new MethodFabImpl(null, ms, null, "{ throw new UnsupportedOperationException(\"Method not implemented\"); }");
262            
263            CtClass ctReturnType = convertClass(ms.getReturnType());
264    
265            CtClass[] ctParameters = convertClasses(ms.getParameterTypes());
266            CtClass[] ctExceptions = convertClasses(ms.getExceptionTypes());
267            
268            CtMethod method = new CtMethod(ctReturnType, ms.getName(), ctParameters, getCtClass());
269            
270            try {
271                method.setModifiers(modifiers);
272                method.setBody(body);
273                method.setExceptionTypes(ctExceptions);
274                
275                getCtClass().addMethod(method);
276            } catch (Exception ex) {
277                
278                throw new ApplicationRuntimeException(EnhanceMessages.unableToAddMethod(ms, getCtClass(), ex), ex);
279            }
280    
281            // Return a MethodFab so the caller can add catches.
282    
283            MethodFab result = new MethodFabImpl(getSource(), ms, method, body);
284    
285            _methods.put(ms, result);
286    
287            return result;
288        }
289        
290        public MethodFab getMethodFab(MethodSignature ms)
291        {
292            return (MethodFab) _methods.get(ms);
293        }
294    
295        public void addConstructor(Class[] parameterTypes, Class[] exceptions, String body)
296        {
297            CtClass[] ctParameters = convertClasses(parameterTypes);
298            CtClass[] ctExceptions = convertClasses(exceptions);
299    
300            try {
301                CtConstructor constructor = new CtConstructor(ctParameters, getCtClass());
302                constructor.setExceptionTypes(ctExceptions);
303                constructor.setBody(body);
304    
305                getCtClass().addConstructor(constructor);
306    
307                _constructors.add(new AddedConstructor(parameterTypes, exceptions, body));
308            } catch (Exception ex) {
309                throw new ApplicationRuntimeException(EnhanceMessages.unableToAddConstructor(getCtClass(), ex), ex);
310            }
311        }
312    }