001    // Copyright 2006, 2007, 2008 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    
015    package org.apache.tapestry5.ioc.services;
016    
017    import org.apache.tapestry5.ioc.ObjectCreator;
018    import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newMap;
019    
020    import static java.lang.String.format;
021    import java.lang.reflect.Method;
022    import java.lang.reflect.Modifier;
023    import java.util.Map;
024    import java.util.concurrent.atomic.AtomicLong;
025    
026    /**
027     * Handy method useful when creating new classes using {@link org.apache.tapestry5.ioc.services.ClassFab}.
028     */
029    public final class ClassFabUtils
030    {
031        private static final AtomicLong UID_GENERATOR = new AtomicLong(System.currentTimeMillis());
032    
033        private static String nextUID()
034        {
035            return Long.toHexString(UID_GENERATOR.getAndIncrement());
036        }
037    
038        /**
039         * Generates a unique class name, which will be in the default package.
040         */
041        public static synchronized String generateClassName(String baseName)
042        {
043            return "$" + baseName + "_" + nextUID();
044        }
045    
046        /**
047         * Returns a class name derived from the provided interfaceClass. The package part of the interface name is stripped
048         * out, and the result passed to {@link #generateClassName(String)}.
049         */
050        public static String generateClassName(Class interfaceClass)
051        {
052            return generateClassName(interfaceClass.getSimpleName());
053        }
054    
055        /**
056         * Javassist needs the class name to be as it appears in source code, even for arrays. Invoking getName() on a Class
057         * instance representing an array returns the internal format (i.e, "[...;" or something). This returns it as it
058         * would appear in Java code.
059         */
060        public static String toJavaClassName(Class inputClass)
061        {
062            if (inputClass.isArray()) return toJavaClassName(inputClass.getComponentType()) + "[]";
063    
064            return inputClass.getName();
065        }
066    
067        /**
068         * Returns true if the method is the standard toString() method. Very few interfaces will ever include this method
069         * as part of the interface, but we have to be sure.
070         */
071        public static boolean isToString(Method method)
072        {
073            if (!method.getName().equals("toString")) return false;
074    
075            if (method.getParameterTypes().length > 0) return false;
076    
077            return method.getReturnType().equals(String.class);
078        }
079    
080        public static Class getPrimitiveType(String primitiveTypeName)
081        {
082            return PRIMITIVE_TYPE_NAME_TO_PRIMITIVE_INFO.get(primitiveTypeName).primitiveType;
083        }
084    
085        private static class PrimitiveInfo
086        {
087            private final Class primitiveType;
088    
089            private final String typeCode;
090    
091            private final Class wrapperType;
092    
093            private final String unwrapMethod;
094    
095            public PrimitiveInfo(Class primitiveType, String typeCode, Class wrapperType, String unwrapMethod)
096            {
097                this.primitiveType = primitiveType;
098                this.typeCode = typeCode;
099                this.wrapperType = wrapperType;
100                this.unwrapMethod = unwrapMethod;
101            }
102        }
103    
104        private static final Map<String, PrimitiveInfo> PRIMITIVE_TYPE_NAME_TO_PRIMITIVE_INFO = newMap();
105        private static final Map<Class, PrimitiveInfo> WRAPPER_TYPE_TO_PRIMITIVE_INFO = newMap();
106    
107        static
108        {
109            add(boolean.class, "Z", Boolean.class, "booleanValue");
110            add(short.class, "S", Short.class, "shortValue");
111            add(int.class, "I", Integer.class, "intValue");
112            add(long.class, "J", Long.class, "longValue");
113            add(float.class, "F", Float.class, "floatValue");
114            add(double.class, "D", Double.class, "doubleValue");
115            add(char.class, "C", Character.class, "charValue");
116            add(byte.class, "B", Byte.class, "byteValue");
117        }
118    
119        private static void add(Class primitiveType, String typeCode, Class wrapperType, String unwrapMethod)
120        {
121            PrimitiveInfo info = new PrimitiveInfo(primitiveType, typeCode, wrapperType, unwrapMethod);
122    
123            PRIMITIVE_TYPE_NAME_TO_PRIMITIVE_INFO.put(primitiveType.getName(), info);
124    
125            WRAPPER_TYPE_TO_PRIMITIVE_INFO.put(wrapperType, info);
126        }
127    
128        /**
129         * Translates types from standard Java format to Java VM format. For example, java.util.Locale remains
130         * java.util.Locale, but int[][] is translated to [[I and java.lang.Object[] to [Ljava.lang.Object;
131         */
132        public static String toJVMBinaryName(String type)
133        {
134            // if it is not an array, just return the type itself
135            if (!type.endsWith("[]")) return type;
136    
137            // if it is an array, convert it to JavaVM-style format
138            StringBuilder buffer = new StringBuilder();
139    
140            while (type.endsWith("[]"))
141            {
142                buffer.append("[");
143                type = type.substring(0, type.length() - 2);
144            }
145    
146            PrimitiveInfo pi = PRIMITIVE_TYPE_NAME_TO_PRIMITIVE_INFO.get(type);
147    
148            if (pi != null)
149            {
150                buffer.append(pi.typeCode);
151            }
152            else
153            {
154                buffer.append("L");
155                buffer.append(type);
156                buffer.append(";");
157            }
158    
159            return buffer.toString();
160        }
161    
162        /**
163         * Given a wrapper type, determines the corresponding primitive type.
164         */
165        public static Class getPrimitiveType(Class wrapperType)
166        {
167            return WRAPPER_TYPE_TO_PRIMITIVE_INFO.get(wrapperType).primitiveType;
168        }
169    
170        /**
171         * Returns the wrapper type for an input type; for most types, this is the type.  For primitive types, it is the
172         * corresponding wrapper type.
173         *
174         * @param type type to check
175         * @return type or corresponding wrapper type
176         */
177        public static Class getWrapperType(Class type)
178        {
179            PrimitiveInfo info = PRIMITIVE_TYPE_NAME_TO_PRIMITIVE_INFO.get(type.getName());
180    
181            return info == null ? type : info.wrapperType;
182        }
183    
184        /**
185         * Takes a reference and casts it to the desired type.  If the desired type is a primitive type, then the reference
186         * is cast to the correct wrapper type and a call to the correct unwrapper method is added. The end result is code
187         * that can be assigned to a field or parameter of the desired type (even if desired type is a primitive).
188         *
189         * @param reference   to be cast
190         * @param desiredType desired object or primitive type
191         * @return Javassist code to peform the cast
192         */
193        public static String castReference(String reference, String desiredType)
194        {
195            if (isPrimitiveType(desiredType))
196            {
197                PrimitiveInfo info = PRIMITIVE_TYPE_NAME_TO_PRIMITIVE_INFO.get(desiredType);
198    
199                return String.format("((%s)%s).%s()",
200                                     info.wrapperType.getName(), reference,
201                                     info.unwrapMethod);
202            }
203    
204            return String.format("(%s)%s", desiredType, reference);
205        }
206    
207    
208        /**
209         * Given a type name, determines if that is the name of a primitive type.
210         */
211        public static boolean isPrimitiveType(String typeName)
212        {
213            return PRIMITIVE_TYPE_NAME_TO_PRIMITIVE_INFO.containsKey(typeName);
214        }
215    
216        /**
217         * Converts a Class to a JVM type code (the way class information is expressed in a class file).
218         */
219        public static String getTypeCode(Class type)
220        {
221            if (type.equals(void.class)) return "V";
222    
223            if (type.isPrimitive()) return PRIMITIVE_TYPE_NAME_TO_PRIMITIVE_INFO.get(type.getName()).typeCode;
224    
225            if (type.isArray()) return "[" + getTypeCode(type.getComponentType());
226    
227            return "L" + type.getName().replace('.', '/') + ";";
228        }
229    
230        /**
231         * Creates a proxy for a given service interface around an {@link org.apache.tapestry5.ioc.ObjectCreator} that can
232         * provide (on demand) an object (implementing the service interface) to delegate to. The ObjectCreator will be
233         * invoked on every method invocation (if it is caching, that should be internal to its implementation).
234         *
235         * @param <T>
236         * @param classFab         used to create the new class
237         * @param serviceInterface the interface the proxy will implement
238         * @param creator          the createor which will provide an instance of the interface
239         * @param description      description to be returned from the proxy's toString() method
240         * @return the instantiated proxy object
241         */
242        public static <T> T createObjectCreatorProxy(ClassFab classFab, Class<T> serviceInterface, ObjectCreator creator,
243                                                     String description)
244        {
245            classFab.addField("_creator", Modifier.PRIVATE | Modifier.FINAL, ObjectCreator.class);
246    
247            classFab.addConstructor(new Class[] { ObjectCreator.class }, null, "_creator = $1;");
248    
249            String body = format("return (%s) _creator.createObject();", serviceInterface.getName());
250    
251            MethodSignature sig = new MethodSignature(serviceInterface, "_delegate", null, null);
252    
253            classFab.addMethod(Modifier.PRIVATE, sig, body);
254    
255            classFab.proxyMethodsToDelegate(serviceInterface, "_delegate()", description);
256            Class proxyClass = classFab.createClass();
257    
258            try
259            {
260                Object proxy = proxyClass.getConstructors()[0].newInstance(creator);
261    
262                return serviceInterface.cast(proxy);
263            }
264            catch (Exception ex)
265            {
266                // This should never happen, so we won't go to a lot of trouble
267                // reporting it.
268                throw new RuntimeException(ex.getMessage(), ex);
269            }
270        }
271    }