001: // Copyright 2006, 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:
015: package org.apache.tapestry.ioc.internal.util;
016:
017: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
018: import static org.apache.tapestry.ioc.internal.util.Defense.notBlank;
019:
020: import java.lang.annotation.Annotation;
021: import java.lang.reflect.Constructor;
022: import java.lang.reflect.Method;
023: import java.lang.reflect.Modifier;
024: import java.util.Arrays;
025: import java.util.Collection;
026: import java.util.Collections;
027: import java.util.Comparator;
028: import java.util.Enumeration;
029: import java.util.Iterator;
030: import java.util.List;
031: import java.util.ListIterator;
032: import java.util.Map;
033:
034: import org.apache.tapestry.ioc.AnnotationProvider;
035: import org.apache.tapestry.ioc.Locatable;
036: import org.apache.tapestry.ioc.Location;
037: import org.apache.tapestry.ioc.ObjectLocator;
038: import org.apache.tapestry.ioc.annotations.Inject;
039: import org.apache.tapestry.ioc.annotations.InjectService;
040: import org.apache.tapestry.ioc.services.ClassFactory;
041:
042: /**
043: * Utilities used within various internal implemenations of Tapestry IOC and the rest of the
044: * tapestry-core framework.
045: */
046:
047: public class InternalUtils {
048: /**
049: * Leading punctiation on member names that is stripped off to form a property name or new
050: * member name.
051: */
052: public static final String NAME_PREFIX = "_$";
053:
054: /**
055: * Converts a method to a user presentable string using a {@link ClassFactory} to obtain a
056: * {@link MethodLocation} (where possible). {@link #asString(Method)} is used under the covers,
057: * to present a detailed, but not excessive, description of the class, method and parameters.
058: *
059: * @param method
060: * method to convert to a string
061: * @param classFactory
062: * used to obtain the {@link MethodLocation}
063: * @return the method formatted for presentation to the user
064: */
065: public static String asString(Method method,
066: ClassFactory classFactory) {
067: Location location = classFactory.getMethodLocation(method);
068:
069: return location != null ? location.toString()
070: : asString(method);
071: }
072:
073: /**
074: * Converts a method to a user presentable string consisting of the containing class name, the
075: * method name, and the short form of the parameter list (the class name of each parameter type,
076: * shorn of the package name portion).
077: *
078: * @param method
079: * @return short string representation
080: */
081: public static String asString(Method method) {
082: StringBuilder buffer = new StringBuilder();
083:
084: buffer.append(method.getDeclaringClass().getName());
085: buffer.append(".");
086: buffer.append(method.getName());
087: buffer.append("(");
088:
089: for (int i = 0; i < method.getParameterTypes().length; i++) {
090: if (i > 0)
091: buffer.append(", ");
092:
093: String name = method.getParameterTypes()[i].getSimpleName();
094:
095: buffer.append(name);
096: }
097:
098: return buffer.append(")").toString();
099: }
100:
101: /** Returns the size of an object array, or null if the array is empty. */
102:
103: public static int size(Object[] array) {
104: return array == null ? 0 : array.length;
105: }
106:
107: /** Strips leading punctuation ("_" and "$") from the provided name. */
108: public static String stripMemberPrefix(String memberName) {
109: StringBuilder builder = new StringBuilder(memberName);
110:
111: // There may be other prefixes we want to strip off, at some point!
112:
113: // Strip off leading characters defined by NAME_PREFIX
114:
115: // This code is really ugly and needs to be fixed.
116:
117: while (true) {
118: char ch = builder.charAt(0);
119:
120: if (InternalUtils.NAME_PREFIX.indexOf(ch) < 0)
121: break;
122:
123: builder.deleteCharAt(0);
124: }
125:
126: return builder.toString();
127: }
128:
129: /**
130: * Strips leading characters defined by {@link InternalUtils#NAME_PREFIX}, then adds the prefix
131: * back in.
132: */
133: public static String createMemberName(String memberName) {
134: return NAME_PREFIX + stripMemberPrefix(memberName);
135: }
136:
137: /**
138: * Converts an enumeration (of Strings) into a sorted list of Strings.
139: */
140: public static List<String> toList(Enumeration e) {
141: List<String> result = newList();
142:
143: while (e.hasMoreElements()) {
144: String name = (String) e.nextElement();
145:
146: result.add(name);
147: }
148:
149: Collections.sort(result);
150:
151: return result;
152: }
153:
154: /**
155: * Finds a specific annotation type within an array of annotations.
156: *
157: * @param <T>
158: * @param annotations
159: * to search
160: * @param annotationClass
161: * to match
162: * @return the annotation instance, if found, or null otherwise
163: */
164: public static <T extends Annotation> T findAnnotation(
165: Annotation[] annotations, Class<T> annotationClass) {
166: for (Annotation a : annotations) {
167: if (annotationClass.isInstance(a))
168: return annotationClass.cast(a);
169: }
170:
171: return null;
172: }
173:
174: @SuppressWarnings("unchecked")
175: private static Object calculateParameterValue(Class parameterType,
176: final Annotation[] parameterAnnotations,
177: ObjectLocator locator, Map<Class, Object> parameterDefaults) {
178: AnnotationProvider provider = new AnnotationProvider() {
179: public <T extends Annotation> T getAnnotation(
180: Class<T> annotationClass) {
181: return findAnnotation(parameterAnnotations,
182: annotationClass);
183: }
184:
185: };
186:
187: InjectService is = provider.getAnnotation(InjectService.class);
188:
189: if (is != null) {
190: String serviceId = is.value();
191:
192: return locator.getService(serviceId, parameterType);
193: }
194:
195: // In the absence of @InjectService, try some autowiring. First, does the
196: // parameter type match on of the resources (the parameter defaults)?
197:
198: if (provider.getAnnotation(Inject.class) == null) {
199: Object result = parameterDefaults.get(parameterType);
200:
201: if (result != null)
202: return result;
203: }
204:
205: // Otherwise, make use of the MasterObjectProvider service to resolve this type (plus
206: // any other information gleaned from additional annotations) into the correct object.
207:
208: return locator.getObject(parameterType, provider);
209: }
210:
211: public static Object[] calculateParametersForMethod(Method method,
212: ObjectLocator locator, Map<Class, Object> parameterDefaults) {
213: Class[] parameterTypes = method.getParameterTypes();
214: Annotation[][] annotations = method.getParameterAnnotations();
215:
216: return calculateParameters(locator, parameterDefaults,
217: parameterTypes, annotations);
218: }
219:
220: public static Object[] calculateParametersForConstructor(
221: Constructor constructor, ObjectLocator locator,
222: Map<Class, Object> parameterDefaults) {
223: Class[] parameterTypes = constructor.getParameterTypes();
224: Annotation[][] annotations = constructor
225: .getParameterAnnotations();
226:
227: return calculateParameters(locator, parameterDefaults,
228: parameterTypes, annotations);
229: }
230:
231: public static Object[] calculateParameters(ObjectLocator locator,
232: Map<Class, Object> parameterDefaults,
233: Class[] parameterTypes, Annotation[][] parameterAnnotations) {
234: int parameterCount = parameterTypes.length;
235:
236: Object[] parameters = new Object[parameterCount];
237:
238: for (int i = 0; i < parameterCount; i++) {
239: parameters[i] = calculateParameterValue(parameterTypes[i],
240: parameterAnnotations[i], locator, parameterDefaults);
241: }
242:
243: return parameters;
244: }
245:
246: /** Joins together some number of elements to form a comma separated list. */
247: public static String join(List elements) {
248: StringBuilder buffer = new StringBuilder();
249: boolean first = true;
250:
251: for (Object o : elements) {
252: if (!first)
253: buffer.append(", ");
254:
255: buffer.append(String.valueOf(o));
256:
257: first = false;
258: }
259:
260: return buffer.toString();
261: }
262:
263: /** Creates a sorted copy of the provided elements, then turns that into a comma separated list. */
264: public static String joinSorted(Collection elements) {
265: List<String> list = newList();
266:
267: for (Object o : elements)
268: list.add(String.valueOf(o));
269:
270: Collections.sort(list);
271:
272: return join(list);
273: }
274:
275: /**
276: * Returns true if the input is null, or is a zero length string (excluding leading/trailing
277: * whitespace).
278: */
279:
280: public static boolean isBlank(String input) {
281: return input == null || input.length() == 0
282: || input.trim().length() == 0;
283: }
284:
285: public static boolean isNonBlank(String input) {
286: return !isBlank(input);
287: }
288:
289: /** Capitalizes a string, converting the first character to uppercase. */
290: public static String capitalize(String input) {
291: if (input.length() == 0)
292: return input;
293:
294: return input.substring(0, 1).toUpperCase() + input.substring(1);
295: }
296:
297: /**
298: * Sniffs the object to see if it is a {@link Location} or {@link Locatable}. Returns null if
299: * null or not convertable to a location.
300: */
301:
302: public static Location locationOf(Object location) {
303: if (location == null)
304: return null;
305:
306: if (location instanceof Location)
307: return (Location) location;
308:
309: if (location instanceof Locatable)
310: return ((Locatable) location).getLocation();
311:
312: return null;
313: }
314:
315: /**
316: * Extracts the string keys from a map and returns them in sorted order. The keys are converted
317: * to strings.
318: *
319: * @param map
320: * the map to extract keys from (may be null)
321: * @return the sorted keys, or the empty set if map is null
322: */
323:
324: public static List<String> sortedKeys(Map map) {
325: if (map == null)
326: return Collections.emptyList();
327:
328: List<String> keys = newList();
329:
330: for (Object o : map.keySet())
331: keys.add(String.valueOf(o));
332:
333: Collections.sort(keys);
334:
335: return keys;
336: }
337:
338: /**
339: * Gets a value from a map (which may be null).
340: *
341: * @param <K>
342: * @param <V>
343: * @param map
344: * the map to extract from (may be null)
345: * @param key
346: * @return the value from the map, or null if the map is null
347: */
348:
349: public static <K, V> V get(Map<K, V> map, K key) {
350: if (map == null)
351: return null;
352:
353: return map.get(key);
354: }
355:
356: /** Returns true if the method provided is a static method. */
357: public static final boolean isStatic(Method method) {
358: return Modifier.isStatic(method.getModifiers());
359: }
360:
361: public static final <T> Iterator<T> reverseIterator(
362: final List<T> list) {
363: final ListIterator<T> normal = list.listIterator(list.size());
364:
365: return new Iterator<T>() {
366: public boolean hasNext() {
367: return normal.hasPrevious();
368: }
369:
370: public T next() {
371: // TODO Auto-generated method stub
372: return normal.previous();
373: }
374:
375: public void remove() {
376: throw new UnsupportedOperationException();
377: }
378:
379: };
380: }
381:
382: /** Return true if the input string contains the marker for symbols that must be expanded. */
383: public static boolean containsSymbols(String input) {
384: return input.contains("${");
385: }
386:
387: /**
388: * Searches the string for the final period ('.') character and returns everything after that.
389: * The input string is generally a fully qualified class name, though tapestry-core also uses
390: * this method for the occasional property expression (which is also dot separated). Returns the
391: * input string unchanged if it does not contain a period character.
392: */
393: public static String lastTerm(String input) {
394: notBlank(input, "input");
395:
396: int dotx = input.lastIndexOf('.');
397:
398: if (dotx < 0)
399: return input;
400:
401: return input.substring(dotx + 1);
402: }
403:
404: /**
405: * Searches a class for the "best" constructor, the public constructor with the most parameters.
406: * Returns null if there are no public constructors. If there is more than one constructor with
407: * the maximum number of parameters, it is not determined which will be returned (don't build a
408: * class like that!).
409: *
410: * @param clazz
411: * to search for a constructor for
412: * @return the constructor to be used to instantiate the class, or null if no appropriate
413: * constructor was found
414: */
415: public static Constructor findAutobuildConstructor(Class clazz) {
416: Constructor[] constructors = clazz.getConstructors();
417:
418: switch (constructors.length) {
419: case 1:
420:
421: return constructors[0];
422:
423: case 0:
424:
425: return null;
426:
427: default:
428: break;
429: }
430:
431: // Choose a constructor with the most parameters.
432:
433: Comparator<Constructor> comparator = new Comparator<Constructor>() {
434: public int compare(Constructor o1, Constructor o2) {
435: return o2.getParameterTypes().length
436: - o1.getParameterTypes().length;
437: }
438: };
439:
440: Arrays.sort(constructors, comparator);
441:
442: return constructors[0];
443: }
444: }
|