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