1 // Copyright 2006, 2007, 2008 The Apache Software Foundation
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package org.apache.tapestry5.internal;
16
17 import org.apache.tapestry5.OptionModel;
18 import org.apache.tapestry5.SelectModel;
19 import org.apache.tapestry5.ioc.Messages;
20 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
21 import org.apache.tapestry5.ioc.internal.util.Defense;
22 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
23
24 import java.util.List;
25 import java.util.Map;
26 import java.util.regex.Pattern;
27
28 /**
29 * Shared utility methods used by various implementation classes.
30 */
31 public class TapestryInternalUtils
32 {
33 private static final String SLASH = "/";
34
35 private static final Pattern SLASH_PATTERN = Pattern.compile(SLASH);
36
37 private static final Pattern NON_WORD_PATTERN = Pattern.compile("[^\\w]");
38
39
40 /**
41 * Capitalizes the string, and inserts a space before each upper case character (or sequence of upper case
42 * characters). Thus "userId" becomes "User Id", etc. Also, converts underscore into space (and capitalizes the
43 * following word), thus "user_id" also becomes "User Id".
44 */
45 public static String toUserPresentable(String id)
46 {
47 StringBuilder builder = new StringBuilder(id.length() * 2);
48
49 char[] chars = id.toCharArray();
50 boolean postSpace = true;
51 boolean upcaseNext = true;
52
53 for (char ch : chars)
54 {
55 if (upcaseNext)
56 {
57 builder.append(Character.toUpperCase(ch));
58 upcaseNext = false;
59
60 continue;
61 }
62
63 if (ch == '_')
64 {
65 builder.append(' ');
66 upcaseNext = true;
67 continue;
68 }
69
70 boolean upperCase = Character.isUpperCase(ch);
71
72 if (upperCase && !postSpace) builder.append(' ');
73
74 builder.append(ch);
75
76 postSpace = upperCase;
77 }
78
79 return builder.toString();
80 }
81
82 public static Map<String, String> mapFromKeysAndValues(String... keysAndValues)
83 {
84 Map<String, String> result = CollectionFactory.newMap();
85
86 int i = 0;
87 while (i < keysAndValues.length)
88 {
89 String key = keysAndValues[i++];
90 String value = keysAndValues[i++];
91
92 result.put(key, value);
93 }
94
95 return result;
96 }
97
98 /**
99 * Converts a string to an {@link OptionModel}. The string is of the form "value=label". If the equals sign is
100 * omitted, then the same value is used for both value and label.
101 *
102 * @param input
103 * @return
104 */
105 public static OptionModel toOptionModel(String input)
106 {
107 Defense.notNull(input, "input");
108
109 int equalsx = input.indexOf('=');
110
111 if (equalsx < 0) return new OptionModelImpl(input);
112
113 String value = input.substring(0, equalsx);
114 String label = input.substring(equalsx + 1);
115
116 return new OptionModelImpl(label, value);
117 }
118
119 /**
120 * Parses a string input into a series of value=label pairs compatible with {@link #toOptionModel(String)}. Splits
121 * on commas. Ignores whitespace around commas.
122 *
123 * @param input comma seperated list of terms
124 * @return list of option models
125 */
126 public static List<OptionModel> toOptionModels(String input)
127 {
128 Defense.notNull(input, "input");
129
130 List<OptionModel> result = CollectionFactory.newList();
131
132 for (String term : input.split(","))
133 result.add(toOptionModel(term.trim()));
134
135 return result;
136 }
137
138 /**
139 * Wraps the result of {@link #toOptionModels(String)} as a {@link SelectModel} (with no option groups).
140 *
141 * @param input
142 * @return
143 */
144 public static SelectModel toSelectModel(String input)
145 {
146 List<OptionModel> options = toOptionModels(input);
147
148 return new SelectModelImpl(null, options);
149 }
150
151 /**
152 * Converts a map entry to an {@link OptionModel}.
153 *
154 * @param input
155 * @return
156 */
157 public static OptionModel toOptionModel(Map.Entry input)
158 {
159 Defense.notNull(input, "input");
160
161 String label = input.getValue() != null ? String.valueOf(input.getValue()) : "";
162
163 return new OptionModelImpl(label, input.getKey());
164 }
165
166 /**
167 * Processes a map input into a series of map entries compatible with {@link #toOptionModel(Map.Entry)}.
168 *
169 * @param input map of elements
170 * @return list of option models
171 */
172 public static <K, V> List<OptionModel> toOptionModels(Map<K, V> input)
173 {
174 Defense.notNull(input, "input");
175
176 List<OptionModel> result = CollectionFactory.newList();
177
178 for (Map.Entry entry : input.entrySet())
179 result.add(toOptionModel(entry));
180
181 return result;
182 }
183
184 /**
185 * Wraps the result of {@link #toOptionModels(Map)} as a {@link SelectModel} (with no option groups).
186 *
187 * @param input
188 * @return
189 */
190 public static <K, V> SelectModel toSelectModel(Map<K, V> input)
191 {
192 List<OptionModel> options = toOptionModels(input);
193
194 return new SelectModelImpl(null, options);
195 }
196
197 /**
198 * Converts an object to an {@link OptionModel}.
199 *
200 * @param input
201 * @return
202 */
203 public static OptionModel toOptionModel(Object input)
204 {
205 String label = (input != null ? String.valueOf(input) : "");
206
207 return new OptionModelImpl(label, input);
208 }
209
210 /**
211 * Processes a list input into a series of objects compatible with {@link #toOptionModel(Object)}.
212 *
213 * @param input list of elements
214 * @return list of option models
215 */
216 public static <E> List<OptionModel> toOptionModels(List<E> input)
217 {
218 Defense.notNull(input, "input");
219
220 List<OptionModel> result = CollectionFactory.newList();
221
222 for (E element : input)
223 result.add(toOptionModel(element));
224
225 return result;
226 }
227
228 /**
229 * Wraps the result of {@link #toOptionModels(List)} as a {@link SelectModel} (with no option groups).
230 *
231 * @param input
232 * @return
233 */
234 public static <E> SelectModel toSelectModel(List<E> input)
235 {
236 List<OptionModel> options = toOptionModels(input);
237
238 return new SelectModelImpl(null, options);
239 }
240
241 /**
242 * Parses a key/value pair where the key and the value are seperated by an equals sign. The key and value are
243 * trimmed of leading and trailing whitespace, and returned as a {@link KeyValue}.
244 *
245 * @param input
246 * @return
247 */
248 public static KeyValue parseKeyValue(String input)
249 {
250 int pos = input.indexOf('=');
251
252 if (pos < 1) throw new IllegalArgumentException(InternalMessages.badKeyValue(input));
253
254 String key = input.substring(0, pos);
255 String value = input.substring(pos + 1);
256
257 return new KeyValue(key.trim(), value.trim());
258 }
259
260
261 /**
262 * Used to convert a property expression into a key that can be used to locate various resources (Blocks, messages,
263 * etc.). Strips out any punctuation characters, leaving just words characters (letters, number and the
264 * underscore).
265 *
266 * @param expression a property expression
267 * @return the expression with punctuation removed
268 */
269 public static String extractIdFromPropertyExpression(String expression)
270 {
271 return replace(expression, NON_WORD_PATTERN, "");
272 }
273
274 /**
275 * Looks for a label within the messages based on the id. If found, it is used, otherwise the name is converted to a
276 * user presentable form.
277 */
278 public static String defaultLabel(String id, Messages messages, String propertyExpression)
279 {
280 String key = id + "-label";
281
282 if (messages.contains(key)) return messages.get(key);
283
284 return toUserPresentable(extractIdFromPropertyExpression(lastTerm(propertyExpression)));
285 }
286
287 /**
288 * Strips a dotted sequence (such as a property expression, or a qualified class name) down to the last term of that
289 * expression, by locating the last period ('.') in the string.
290 */
291 public static String lastTerm(String input)
292 {
293 int dotx = input.lastIndexOf('.');
294
295 return input.substring(dotx + 1);
296 }
297
298 /**
299 * Converts an list of strings into a space-separated string combining them all, suitable for use as an HTML class
300 * attribute value.
301 *
302 * @param classes classes to combine
303 * @return the joined classes, or null if classes is empty
304 */
305 public static String toClassAttributeValue(List<String> classes)
306 {
307 if (classes.isEmpty()) return null;
308
309 return InternalUtils.join(classes, " ");
310 }
311
312
313 /**
314 * Converts an enum to a label string, allowing for overrides from a message catalog.
315 * <p/>
316 * <ul> <li>As key <em>prefix</em>.<em>name</em> if present. Ex: "ElementType.LOCAL_VARIABLE" <li>As key
317 * <em>name</em> if present, i.e., "LOCAL_VARIABLE". <li>As a user-presentable version of the name, i.e., "Local
318 * Variable". </ul>
319 *
320 * @param messages the messages to search for the label
321 * @param prefix
322 * @param value to get a label for
323 * @return the label
324 */
325 public static String getLabelForEnum(Messages messages, String prefix, Enum value)
326 {
327 String name = value.name();
328
329 String key = prefix + "." + name;
330
331 if (messages.contains(key)) return messages.get(key);
332
333 if (messages.contains(name)) return messages.get(name);
334
335 return toUserPresentable(name.toLowerCase());
336 }
337
338 public static String getLabelForEnum(Messages messages, Enum value)
339 {
340 String prefix = lastTerm(value.getClass().getName());
341
342 return getLabelForEnum(messages, prefix, value);
343 }
344
345 private static String replace(String input, Pattern pattern, String replacement)
346 {
347 return pattern.matcher(input).replaceAll(replacement);
348 }
349
350 /**
351 * Determines if the two values are equal. They are equal if they are the exact same value (including if they are
352 * both null). Otherwise standard equals() comparison is used.
353 *
354 * @param <T>
355 * @param left value to compare, possibly null
356 * @param right value to compare, possibly null
357 * @return true if same value, both null, or equal
358 */
359 public static <T> boolean isEqual(T left, T right)
360 {
361 if (left == right) return true;
362
363 if (left == null) return right == null;
364
365 return left.equals(right);
366 }
367
368
369 /**
370 * Splits a path at each slash.
371 */
372 public static String[] splitPath(String path)
373 {
374 return SLASH_PATTERN.split(path);
375 }
376 }