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.internal;
016:
017: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
018: import static org.apache.tapestry.ioc.internal.util.Defense.notNull;
019:
020: import java.io.Closeable;
021: import java.io.IOException;
022: import java.lang.reflect.Method;
023: import java.util.Collections;
024: import java.util.List;
025: import java.util.Map;
026:
027: import org.apache.commons.codec.DecoderException;
028: import org.apache.commons.codec.EncoderException;
029: import org.apache.commons.codec.net.URLCodec;
030: import org.apache.tapestry.OptionModel;
031: import org.apache.tapestry.PropertyConduit;
032: import org.apache.tapestry.SelectModel;
033: import org.apache.tapestry.beaneditor.Order;
034: import org.apache.tapestry.ioc.Location;
035: import org.apache.tapestry.ioc.Messages;
036: import org.apache.tapestry.ioc.internal.util.CollectionFactory;
037: import org.apache.tapestry.ioc.internal.util.Defense;
038: import org.apache.tapestry.ioc.services.ClassFactory;
039: import org.apache.tapestry.ioc.services.ClassPropertyAdapter;
040: import org.apache.tapestry.ioc.services.PropertyAdapter;
041:
042: /** Shared utility methods used by various implementation classes. */
043: public class TapestryInternalUtils {
044: private static final URLCodec CODEC = new URLCodec();
045:
046: private TapestryInternalUtils() {
047: // Prevent instantiation.
048: }
049:
050: public static final void close(Closeable stream) {
051: if (stream != null)
052: try {
053: stream.close();
054: } catch (IOException ex) {
055: // Ignore.
056: }
057: }
058:
059: /**
060: * Capitalizes the string, and inserts a space before each upper case character (or sequence of
061: * upper case characters). Thus "userId" becomes "User Id", etc. Also, converts underscore into
062: * space (and capitalizes the following word), thus "user_id" also becomes "User Id".
063: */
064: public static String toUserPresentable(String id) {
065: StringBuilder builder = new StringBuilder(id.length() * 2);
066:
067: char[] chars = id.toCharArray();
068: boolean postSpace = true;
069: boolean upcaseNext = true;
070:
071: for (int i = 0; i < chars.length; i++) {
072: char ch = chars[i];
073:
074: if (upcaseNext) {
075: builder.append(Character.toUpperCase(ch));
076: upcaseNext = false;
077:
078: continue;
079: }
080:
081: if (ch == '_') {
082: builder.append(' ');
083: upcaseNext = true;
084: continue;
085: }
086:
087: boolean upperCase = Character.isUpperCase(ch);
088:
089: if (upperCase && !postSpace)
090: builder.append(' ');
091:
092: builder.append(ch);
093:
094: postSpace = upperCase;
095: }
096:
097: return builder.toString();
098: }
099:
100: public static Map<String, String> mapFromKeysAndValues(
101: String... keysAndValues) {
102: Map<String, String> result = CollectionFactory.newMap();
103:
104: int i = 0;
105: while (i < keysAndValues.length) {
106: String key = keysAndValues[i++];
107: String value = keysAndValues[i++];
108:
109: result.put(key, value);
110: }
111:
112: return result;
113: }
114:
115: /**
116: * Converts a string to an {@link OptionModel}. The string is of the form "value=label". If the
117: * equals sign is omitted, then the same value is used for both value and label.
118: *
119: * @param input
120: * @return
121: */
122: public static OptionModel toOptionModel(String input) {
123: Defense.notNull(input, "input");
124:
125: int equalsx = input.indexOf('=');
126:
127: if (equalsx < 0)
128: return new OptionModelImpl(input, false, input);
129:
130: String value = input.substring(0, equalsx);
131: String label = input.substring(equalsx + 1);
132:
133: return new OptionModelImpl(label, false, value);
134: }
135:
136: /**
137: * Parses a string input into a series of value=label pairs compatible with
138: * {@link #toOptionModel(String)}. Splits on commas. Ignores whitespace around commas.
139: *
140: * @param input
141: * comma seperated list of terms
142: * @return list of option models
143: */
144: public static List<OptionModel> toOptionModels(String input) {
145: Defense.notNull(input, "input");
146:
147: List<OptionModel> result = newList();
148:
149: for (String term : input.split(","))
150: result.add(toOptionModel(term.trim()));
151:
152: return result;
153: }
154:
155: /**
156: * Wraps the result of {@link #toOptionModels(String)} as a {@link SelectModel} (with no option
157: * groups).
158: *
159: * @param input
160: * @return
161: */
162: public static SelectModel toSelectModel(String input) {
163: List<OptionModel> options = toOptionModels(input);
164:
165: return new SelectModelImpl(null, options);
166: }
167:
168: /**
169: * Converts a map entry to an {@link OptionModel}.
170: *
171: * @param input
172: * @return
173: */
174: public static OptionModel toOptionModel(Map.Entry input) {
175: notNull(input, "input");
176:
177: String label = input.getValue() != null ? String.valueOf(input
178: .getValue()) : "";
179:
180: return new OptionModelImpl(label, false, input.getKey());
181: }
182:
183: /**
184: * Processes a map input into a series of map entries compatible with
185: * {@link #toOptionModel(Map.Entry)}.
186: *
187: * @param input
188: * map of elements
189: * @return list of option models
190: */
191: public static <K, V> List<OptionModel> toOptionModels(
192: Map<K, V> input) {
193: Defense.notNull(input, "input");
194:
195: List<OptionModel> result = newList();
196:
197: for (Map.Entry entry : input.entrySet())
198: result.add(toOptionModel(entry));
199:
200: return result;
201: }
202:
203: /**
204: * Wraps the result of {@link #toOptionModels(Map)} as a {@link SelectModel} (with no option
205: * groups).
206: *
207: * @param input
208: * @return
209: */
210: public static <K, V> SelectModel toSelectModel(Map<K, V> input) {
211: List<OptionModel> options = toOptionModels(input);
212:
213: return new SelectModelImpl(null, options);
214: }
215:
216: /**
217: * Converts an object to an {@link OptionModel}.
218: *
219: * @param input
220: * @return
221: */
222: public static OptionModel toOptionModel(Object input) {
223: String value = (input != null ? String.valueOf(input) : "");
224:
225: return new OptionModelImpl(value, false, value);
226: }
227:
228: /**
229: * Processes a list input into a series of objects compatible with
230: * {@link #toOptionModel(Object)}.
231: *
232: * @param input
233: * list of elements
234: * @return list of option models
235: */
236: public static <E> List<OptionModel> toOptionModels(List<E> input) {
237: Defense.notNull(input, "input");
238:
239: List<OptionModel> result = newList();
240:
241: for (E element : input)
242: result.add(toOptionModel(element));
243:
244: return result;
245: }
246:
247: /**
248: * Wraps the result of {@link #toOptionModels(List)} as a {@link SelectModel} (with no option
249: * groups).
250: *
251: * @param input
252: * @return
253: */
254: public static <E> SelectModel toSelectModel(List<E> input) {
255: List<OptionModel> options = toOptionModels(input);
256:
257: return new SelectModelImpl(null, options);
258: }
259:
260: /**
261: * Parses a key/value pair where the key and the value are seperated by an equals sign. The key
262: * and value are trimmed of leading and trailing whitespace, and returned as a {@link KeyValue}.
263: *
264: * @param input
265: * @return
266: */
267: public static KeyValue parseKeyValue(String input) {
268: int pos = input.indexOf('=');
269:
270: if (pos < 1)
271: throw new IllegalArgumentException(InternalMessages
272: .badKeyValue(input));
273:
274: String key = input.substring(0, pos);
275: String value = input.substring(pos + 1);
276:
277: return new KeyValue(key.trim(), value.trim());
278: }
279:
280: public static int defaultOrder(PropertyConduit conduit) {
281: if (conduit == null)
282: return 0;
283:
284: Order order = conduit.getAnnotation(Order.class);
285:
286: return order == null ? 0 : order.value();
287: }
288:
289: /**
290: * Used to convert a property expression into a key that can be used to locate various resources
291: * (Blocks, messages, etc.). Strips out any punctuation characters, leaving just words
292: * characters (letters, number and the underscore).
293: *
294: * @param expression
295: * @return
296: */
297: public static String extractIdFromPropertyExpression(
298: String expression) {
299: return expression.replaceAll("[^\\w]", "");
300: }
301:
302: /**
303: * Looks for a label within the messages based on the id. If found, it is used, otherwise the
304: * name is converted to a user presentable form.
305: */
306: public static String defaultLabel(String id, Messages messages,
307: String propertyExpression) {
308: String key = id + "-label";
309:
310: if (messages.contains(key))
311: return messages.get(key);
312:
313: return toUserPresentable(extractIdFromPropertyExpression(lastTerm(propertyExpression)));
314: }
315:
316: /**
317: * Strips a dotted sequence (such as a property expression, or a qualified class name) down to
318: * the last term of that expression, by locating the last period ('.') in the string.
319: */
320: public static String lastTerm(String input) {
321: int dotx = input.lastIndexOf('.');
322:
323: return input.substring(dotx + 1);
324: }
325:
326: private static class PropertyOrder implements
327: Comparable<PropertyOrder> {
328: final String _propertyName;
329:
330: final int _classDepth;
331:
332: final int _sortKey;
333:
334: public PropertyOrder(final String propertyName, int classDepth,
335: int sortKey) {
336: _propertyName = propertyName;
337: _classDepth = classDepth;
338: _sortKey = sortKey;
339: }
340:
341: public int compareTo(PropertyOrder o) {
342: int result = _classDepth - o._classDepth;
343:
344: if (result == 0)
345: result = _sortKey - o._sortKey;
346:
347: if (result == 0)
348: result = _propertyName.compareTo(o._propertyName);
349:
350: return result;
351: }
352: }
353:
354: /**
355: * Sorts the property names into presentation order. Filters out any properties that have an
356: * explicit {@link Order}, leaving the remainder. Estimates each propertie's position based on
357: * the relative position of the property's getter. The code assumes that all methods are
358: * readable (have a getter method).
359: *
360: * @param classAdapter
361: * defines the bean that contains the properties
362: * @param classFactory
363: * used to access method line number information
364: * @param propertyNames
365: * the initial set of property names
366: * @return propertyNames filtered and sorted
367: */
368: public static List<String> orderProperties(
369: ClassPropertyAdapter classAdapter,
370: ClassFactory classFactory, List<String> propertyNames) {
371: List<PropertyOrder> properties = newList();
372:
373: for (String name : propertyNames) {
374: PropertyAdapter pa = classAdapter.getPropertyAdapter(name);
375:
376: if (pa.getAnnotation(Order.class) != null)
377: continue;
378:
379: Method readMethod = pa.getReadMethod();
380:
381: Location location = classFactory
382: .getMethodLocation(readMethod);
383:
384: properties.add(new PropertyOrder(name,
385: computeDepth(readMethod), location.getLine()));
386: }
387:
388: Collections.sort(properties);
389:
390: List<String> result = newList();
391:
392: for (PropertyOrder po : properties)
393: result.add(po._propertyName);
394:
395: return result;
396: }
397:
398: private static int computeDepth(Method method) {
399: int depth = 0;
400: Class c = method.getDeclaringClass();
401:
402: // When the method originates in an interface, the parent may be null, not Object.
403:
404: while (c != null && c != Object.class) {
405: depth++;
406: c = c.getSuperclass();
407: }
408:
409: return depth;
410: }
411:
412: /**
413: * @param messages
414: * the messages to search for the label
415: * @param prefix
416: * @param value
417: * to get a label for
418: * @return the label
419: */
420: public static String getLabelForEnum(Messages messages,
421: String prefix, Enum value) {
422: String name = value.name();
423:
424: String key = prefix + "." + name;
425:
426: if (messages.contains(key))
427: return messages.get(key);
428:
429: if (messages.contains(name))
430: return messages.get(name);
431:
432: return toUserPresentable(name.toLowerCase());
433: }
434:
435: public static String getLabelForEnum(Messages messages, Enum value) {
436: String prefix = lastTerm(value.getClass().getName());
437:
438: return getLabelForEnum(messages, prefix, value);
439: }
440:
441: public static String urlEncode(String input) {
442: try {
443: return CODEC.encode(input);
444: } catch (EncoderException ex) {
445: throw new RuntimeException(ex);
446: }
447: }
448:
449: public static String urlDecode(String input) {
450: try {
451: return CODEC.decode(input);
452: } catch (DecoderException ex) {
453: throw new RuntimeException(ex);
454: }
455: }
456:
457: /**
458: * Determines if the two values are equal. They are equal if they are the exact same value
459: * (including if they are both null). Otherwise standard equals() comparison is used.
460: *
461: * @param <T>
462: * @param left
463: * @param right
464: * @return
465: */
466: public static <T> boolean isEqual(T left, T right) {
467: if (left == right)
468: return true;
469:
470: if (left == null)
471: return right == null;
472:
473: return left.equals(right);
474: }
475: }
|