001// Copyright 2006-2014 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. 014package org.apache.tapestry5.ioc.internal.util; 015 016import java.lang.annotation.Annotation; 017import java.lang.reflect.Method; 018import java.util.Collection; 019import java.util.Collections; 020import java.util.List; 021import java.util.Map; 022import java.util.regex.Matcher; 023import java.util.regex.Pattern; 024 025import org.apache.tapestry5.ioc.AnnotationProvider; 026import org.apache.tapestry5.ioc.Locatable; 027import org.apache.tapestry5.ioc.Location; 028import org.apache.tapestry5.ioc.Messages; 029import org.apache.tapestry5.ioc.internal.NullAnnotationProvider; 030 031/** 032 * Utility methods class for the Commons package. 033 */ 034public class InternalCommonsUtils { 035 036 /** 037 * @since 5.3 038 */ 039 public final static AnnotationProvider NULL_ANNOTATION_PROVIDER = new NullAnnotationProvider(); 040 private static final Pattern NON_WORD_PATTERN = Pattern.compile("[^\\w]"); 041 042 /** 043 * Adds a value to a specially organized map where the values are lists of objects. This somewhat simulates a map 044 * that allows multiple values for the same key. 045 * 046 * @param map 047 * to store value into 048 * @param key 049 * for which a value is added 050 * @param value 051 * to add 052 * @param <K> 053 * the type of key 054 * @param <V> 055 * the type of the list 056 */ 057 public static <K, V> void addToMapList(Map<K, List<V>> map, K key, V value) 058 { 059 List<V> list = map.get(key); 060 061 if (list == null) 062 { 063 list = CollectionFactory.newList(); 064 map.put(key, list); 065 } 066 067 list.add(value); 068 } 069 070 /** 071 * Sniffs the object to see if it is a {@link Location} or {@link Locatable}. Returns null if null or not 072 * convertable to a location. 073 */ 074 075 public static Location locationOf(Object location) 076 { 077 if (location == null) 078 return null; 079 080 if (location instanceof Location) 081 return (Location) location; 082 083 if (location instanceof Locatable) 084 return ((Locatable) location).getLocation(); 085 086 return null; 087 } 088 089 public static AnnotationProvider toAnnotationProvider(final Method element) 090 { 091 if (element == null) 092 return NULL_ANNOTATION_PROVIDER; 093 094 return new AnnotationProvider() 095 { 096 @Override 097 public <T extends Annotation> T getAnnotation(Class<T> annotationClass) 098 { 099 return element.getAnnotation(annotationClass); 100 } 101 }; 102 } 103 104 /** 105 * Used to convert a property expression into a key that can be used to locate various resources (Blocks, messages, 106 * etc.). Strips out any punctuation characters, leaving just words characters (letters, number and the 107 * underscore). 108 * 109 * @param expression a property expression 110 * @return the expression with punctuation removed 111 */ 112 public static String extractIdFromPropertyExpression(String expression) 113 { 114 return replace(expression, NON_WORD_PATTERN, ""); 115 } 116 117 public static String replace(String input, Pattern pattern, String replacement) 118 { 119 return pattern.matcher(input).replaceAll(replacement); 120 } 121 122 /** 123 * Looks for a label within the messages based on the id. If found, it is used, otherwise the name is converted to a 124 * user presentable form. 125 */ 126 public static String defaultLabel(String id, Messages messages, String propertyExpression) 127 { 128 String key = id + "-label"; 129 130 if (messages.contains(key)) 131 return messages.get(key); 132 133 return toUserPresentable(extractIdFromPropertyExpression(InternalCommonsUtils.lastTerm(propertyExpression))); 134 } 135 136 /** 137 * Capitalizes the string, and inserts a space before each upper case character (or sequence of upper case 138 * characters). Thus "userId" becomes "User Id", etc. Also, converts underscore into space (and capitalizes the 139 * following word), thus "user_id" also becomes "User Id". 140 */ 141 public static String toUserPresentable(String id) 142 { 143 StringBuilder builder = new StringBuilder(id.length() * 2); 144 145 char[] chars = id.toCharArray(); 146 boolean postSpace = true; 147 boolean upcaseNext = true; 148 149 for (char ch : chars) 150 { 151 if (upcaseNext) 152 { 153 builder.append(Character.toUpperCase(ch)); 154 upcaseNext = false; 155 156 continue; 157 } 158 159 if (ch == '_') 160 { 161 builder.append(' '); 162 upcaseNext = true; 163 continue; 164 } 165 166 boolean upperCase = Character.isUpperCase(ch); 167 168 if (upperCase && !postSpace) 169 builder.append(' '); 170 171 builder.append(ch); 172 173 postSpace = upperCase; 174 } 175 176 return builder.toString(); 177 } 178 179 /** 180 * @since 5.3 181 */ 182 public static AnnotationProvider toAnnotationProvider(final Class element) 183 { 184 return new AnnotationProvider() 185 { 186 @Override 187 public <T extends Annotation> T getAnnotation(Class<T> annotationClass) 188 { 189 return annotationClass.cast(element.getAnnotation(annotationClass)); 190 } 191 }; 192 } 193 194 /** 195 * Pattern used to eliminate leading and trailing underscores and dollar signs. 196 */ 197 static final Pattern NAME_PATTERN = Pattern.compile("^[_|$]*([\\p{javaJavaIdentifierPart}]+?)[_|$]*$", 198 Pattern.CASE_INSENSITIVE); 199 200 /** 201 * Converts a method to a user presentable string consisting of the containing class name, the method name, and the 202 * short form of the parameter list (the class name of each parameter type, shorn of the package name portion). 203 * 204 * @param method 205 * @return short string representation 206 */ 207 public static String asString(Method method) 208 { 209 StringBuilder buffer = new StringBuilder(); 210 211 buffer.append(method.getDeclaringClass().getName()); 212 buffer.append('.'); 213 buffer.append(method.getName()); 214 buffer.append('('); 215 216 for (int i = 0; i < method.getParameterTypes().length; i++) 217 { 218 if (i > 0) 219 buffer.append(", "); 220 221 String name = method.getParameterTypes()[i].getSimpleName(); 222 223 buffer.append(name); 224 } 225 226 return buffer.append(')').toString(); 227 } 228 229 /** 230 * Strips leading "_" and "$" and trailing "_" from the name. 231 */ 232 public static String stripMemberName(String memberName) 233 { 234 assert InternalCommonsUtils.isNonBlank(memberName); 235 Matcher matcher = NAME_PATTERN.matcher(memberName); 236 237 if (!matcher.matches()) 238 throw new IllegalArgumentException(String.format("Input '%s' is not a valid Java identifier.", memberName)); 239 240 return matcher.group(1); 241 } 242 243 /** 244 * Joins together some number of elements to form a comma separated list. 245 */ 246 public static String join(List elements) 247 { 248 return InternalCommonsUtils.join(elements, ", "); 249 } 250 251 /** 252 * Joins together some number of elements. If a value in the list is the empty string, it is replaced with the 253 * string "(blank)". 254 * 255 * @param elements 256 * objects to be joined together 257 * @param separator 258 * used between elements when joining 259 */ 260 public static String join(List elements, String separator) 261 { 262 switch (elements.size()) 263 { 264 case 0: 265 return ""; 266 267 case 1: 268 return String.valueOf(elements.get(0)); 269 270 default: 271 272 StringBuilder buffer = new StringBuilder(); 273 boolean first = true; 274 275 for (Object o : elements) 276 { 277 if (!first) 278 buffer.append(separator); 279 280 String string = String.valueOf(o); 281 282 if (string.equals("")) 283 string = "(blank)"; 284 285 buffer.append(string); 286 287 first = false; 288 } 289 290 return buffer.toString(); 291 } 292 } 293 294 /** 295 * Creates a sorted copy of the provided elements, then turns that into a comma separated list. 296 * 297 * @return the elements converted to strings, sorted, joined with comma ... or "(none)" if the elements are null or 298 * empty 299 */ 300 public static String joinSorted(Collection elements) 301 { 302 if (elements == null || elements.isEmpty()) 303 return "(none)"; 304 305 List<String> list = CollectionFactory.newList(); 306 307 for (Object o : elements) 308 list.add(String.valueOf(o)); 309 310 Collections.sort(list); 311 312 return join(list); 313 } 314 315 /** 316 * Returns true if the input is null, or is a zero length string (excluding leading/trailing whitespace). 317 */ 318 319 public static boolean isBlank(String input) 320 { 321 return input == null || input.length() == 0 || input.trim().length() == 0; 322 } 323 324 /** 325 * Capitalizes a string, converting the first character to uppercase. 326 */ 327 public static String capitalize(String input) 328 { 329 if (input.length() == 0) 330 return input; 331 332 return input.substring(0, 1).toUpperCase() + input.substring(1); 333 } 334 335 public static boolean isNonBlank(String input) 336 { 337 return !isBlank(input); 338 } 339 340 /** 341 * Return true if the input string contains the marker for symbols that must be expanded. 342 */ 343 public static boolean containsSymbols(String input) 344 { 345 return input.contains("${"); 346 } 347 348 /** 349 * Searches the string for the final period ('.') character and returns everything after that. The input string is 350 * generally a fully qualified class name, though tapestry-core also uses this method for the occasional property 351 * expression (which is also dot separated). Returns the input string unchanged if it does not contain a period 352 * character. 353 */ 354 public static String lastTerm(String input) 355 { 356 assert isNonBlank(input); 357 int dotx = input.lastIndexOf('.'); 358 359 if (dotx < 0) 360 return input; 361 362 return input.substring(dotx + 1); 363 } 364 365 /** 366 * Extracts the string keys from a map and returns them in sorted order. The keys are converted to strings. 367 * 368 * @param map 369 * the map to extract keys from (may be null) 370 * @return the sorted keys, or the empty set if map is null 371 */ 372 373 public static List<String> sortedKeys(Map map) 374 { 375 if (map == null) 376 return Collections.emptyList(); 377 378 List<String> keys = CollectionFactory.newList(); 379 380 for (Object o : map.keySet()) 381 keys.add(String.valueOf(o)); 382 383 Collections.sort(keys); 384 385 return keys; 386 } 387 388}