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 }