001// Licensed under the Apache License, Version 2.0 (the "License"); 002// you may not use this file except in compliance with the License. 003// You may obtain a copy of the License at 004// 005// http://www.apache.org/licenses/LICENSE-2.0 006// 007// Unless required by applicable law or agreed to in writing, software 008// distributed under the License is distributed on an "AS IS" BASIS, 009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 010// See the License for the specific language governing permissions and 011// limitations under the License. 012 013package org.apache.tapestry5.internal; 014 015import org.apache.tapestry5.*; 016import org.apache.tapestry5.beanmodel.PropertyConduit; 017import org.apache.tapestry5.beanmodel.PropertyConduit2; 018import org.apache.tapestry5.beanmodel.internal.InternalPropertyConduit; 019import org.apache.tapestry5.commons.Messages; 020import org.apache.tapestry5.commons.Resource; 021import org.apache.tapestry5.commons.util.CollectionFactory; 022import org.apache.tapestry5.commons.util.CommonsUtils; 023import org.apache.tapestry5.func.Mapper; 024import org.apache.tapestry5.http.Link; 025import org.apache.tapestry5.ioc.Orderable; 026import org.apache.tapestry5.ioc.internal.util.InternalUtils; 027import org.apache.tapestry5.services.ComponentEventRequestParameters; 028import org.apache.tapestry5.services.LinkCreationListener; 029import org.apache.tapestry5.services.LinkCreationListener2; 030import org.apache.tapestry5.services.PageRenderRequestParameters; 031import org.apache.tapestry5.services.javascript.StylesheetLink; 032 033import java.io.IOException; 034import java.io.InputStream; 035import java.io.OutputStream; 036import java.lang.annotation.Annotation; 037import java.lang.ref.Reference; 038import java.lang.reflect.Type; 039import java.util.Arrays; 040import java.util.List; 041import java.util.Map; 042import java.util.regex.Pattern; 043 044/** 045 * Shared utility methods used by various implementation classes. 046 */ 047@SuppressWarnings("all") 048public class TapestryInternalUtils 049{ 050 private static final Pattern NON_WORD_PATTERN = Pattern.compile("[^\\w]"); 051 052 private static final int BUFFER_SIZE = 5000; 053 054 /** 055 * Capitalizes the string, and inserts a space before each upper case character (or sequence of upper case 056 * characters). Thus "userId" becomes "User Id", etc. Also, converts underscore into space (and capitalizes the 057 * following word), thus "user_id" also becomes "User Id". 058 */ 059 public static String toUserPresentable(String id) 060 { 061 return InternalUtils.toUserPresentable(id); 062 } 063 064 public static Map<String, String> mapFromKeysAndValues(String... keysAndValues) 065 { 066 Map<String, String> result = CollectionFactory.newMap(); 067 068 int i = 0; 069 while (i < keysAndValues.length) 070 { 071 String key = keysAndValues[i++]; 072 String value = keysAndValues[i++]; 073 074 result.put(key, value); 075 } 076 077 return result; 078 } 079 080 /** 081 * Converts a string to an {@link OptionModel}. The string is of the form "value=label". If the equals sign is 082 * omitted, then the same value is used for both value and label. 083 */ 084 public static OptionModel toOptionModel(String input) 085 { 086 assert input != null; 087 int equalsx = input.indexOf('='); 088 089 if (equalsx < 0) 090 return new OptionModelImpl(input); 091 092 String value = input.substring(0, equalsx); 093 String label = input.substring(equalsx + 1); 094 095 return new OptionModelImpl(label, value); 096 } 097 098 /** 099 * Parses a string input into a series of value=label pairs compatible with {@link #toOptionModel(String)}. Splits 100 * on commas. Ignores whitespace around commas. 101 * 102 * @param input comma seperated list of terms 103 * @return list of option models 104 */ 105 public static List<OptionModel> toOptionModels(String input) 106 { 107 assert input != null; 108 List<OptionModel> result = CollectionFactory.newList(); 109 110 for (String term : input.split(",")) 111 result.add(toOptionModel(term.trim())); 112 113 return result; 114 } 115 116 /** 117 * Wraps the result of {@link #toOptionModels(String)} as a {@link SelectModel} (with no option groups). 118 * 119 * See TAP5-2184 for why this ends up causing some trouble! 120 */ 121 public static SelectModel toSelectModel(String input) 122 { 123 List<OptionModel> options = toOptionModels(input); 124 125 return new SelectModelImpl(null, options); 126 } 127 128 /** 129 * Converts a map entry to an {@link OptionModel}. 130 */ 131 public static OptionModel toOptionModel(Map.Entry input) 132 { 133 assert input != null; 134 String label = input.getValue() != null ? String.valueOf(input.getValue()) : ""; 135 136 return new OptionModelImpl(label, input.getKey()); 137 } 138 139 /** 140 * Processes a map input into a series of map entries compatible with {@link #toOptionModel(Map.Entry)}. 141 * 142 * @param input map of elements 143 * @return list of option models 144 */ 145 public static <K, V> List<OptionModel> toOptionModels(Map<K, V> input) 146 { 147 assert input != null; 148 List<OptionModel> result = CollectionFactory.newList(); 149 150 for (Map.Entry entry : input.entrySet()) 151 result.add(toOptionModel(entry)); 152 153 return result; 154 } 155 156 /** 157 * Wraps the result of {@link #toOptionModels(Map)} as a {@link SelectModel} (with no option groups). 158 */ 159 public static <K, V> SelectModel toSelectModel(Map<K, V> input) 160 { 161 List<OptionModel> options = toOptionModels(input); 162 163 return new SelectModelImpl(null, options); 164 } 165 166 /** 167 * Converts an object to an {@link OptionModel}. 168 */ 169 public static OptionModel toOptionModel(Object input) 170 { 171 String label = (input != null ? String.valueOf(input) : ""); 172 173 return new OptionModelImpl(label, input); 174 } 175 176 /** 177 * Processes a list input into a series of objects compatible with {@link #toOptionModel(Object)}. 178 * 179 * @param input list of elements 180 * @return list of option models 181 */ 182 public static <E> List<OptionModel> toOptionModels(List<E> input) 183 { 184 assert input != null; 185 List<OptionModel> result = CollectionFactory.newList(); 186 187 for (E element : input) 188 result.add(toOptionModel(element)); 189 190 return result; 191 } 192 193 /** 194 * Wraps the result of {@link #toOptionModels(List)} as a {@link SelectModel} (with no option groups). 195 */ 196 public static <E> SelectModel toSelectModel(List<E> input) 197 { 198 List<OptionModel> options = toOptionModels(input); 199 200 return new SelectModelImpl(null, options); 201 } 202 203 /** 204 * Parses a key/value pair where the key and the value are seperated by an equals sign. The key and value are 205 * trimmed of leading and trailing whitespace, and returned as a {@link KeyValue}. 206 */ 207 public static KeyValue parseKeyValue(String input) 208 { 209 int pos = input.indexOf('='); 210 211 if (pos < 1) 212 throw new IllegalArgumentException(String.format("Key/value pair '%s' is not properly formatted (it does not contain an equals sign).", input)); 213 214 String key = input.substring(0, pos); 215 String value = input.substring(pos + 1); 216 217 return new KeyValue(key.trim(), value.trim()); 218 } 219 220 /** 221 * Used to convert a property expression into a key that can be used to locate various resources (Blocks, messages, 222 * etc.). Strips out any punctuation characters, leaving just words characters (letters, number and the 223 * underscore). 224 * 225 * @param expression a property expression 226 * @return the expression with punctuation removed 227 */ 228 public static String extractIdFromPropertyExpression(String expression) 229 { 230 return InternalUtils.extractIdFromPropertyExpression(expression); 231 } 232 233 /** 234 * Looks for a label within the messages based on the id. If found, it is used, otherwise the name is converted to a 235 * user presentable form. 236 */ 237 public static String defaultLabel(String id, Messages messages, String propertyExpression) 238 { 239 return InternalUtils.defaultLabel(id, messages, propertyExpression); 240 } 241 242 /** 243 * Strips a dotted sequence (such as a property expression, or a qualified class name) down to the last term of that 244 * expression, by locating the last period ('.') in the string. 245 */ 246 public static String lastTerm(String input) 247 { 248 int dotx = input.lastIndexOf('.'); 249 250 return input.substring(dotx + 1); 251 } 252 253 /** 254 * Converts an list of strings into a space-separated string combining them all, suitable for use as an HTML class 255 * attribute value. 256 * 257 * @param classes classes to combine 258 * @return the joined classes, or null if classes is empty 259 */ 260 public static String toClassAttributeValue(List<String> classes) 261 { 262 if (classes.isEmpty()) 263 return null; 264 265 return InternalUtils.join(classes, " "); 266 } 267 268 /** 269 * Converts an enum to a label string, allowing for overrides from a message catalog. 270 * 271 * <ul> 272 * <li>As key <em>prefix</em>.<em>name</em> if present. Ex: "ElementType.LOCAL_VARIABLE" 273 * <li>As key <em>name</em> if present, i.e., "LOCAL_VARIABLE". 274 * <li>As a user-presentable version of the name, i.e., "Local Variable". 275 * </ul> 276 * 277 * @param messages the messages to search for the label 278 * @param prefix prepended to key 279 * @param value to get a label for 280 * @return the label 281 */ 282 public static String getLabelForEnum(Messages messages, String prefix, Enum value) 283 { 284 String name = value.name(); 285 286 String key = prefix + "." + name; 287 288 if (messages.contains(key)) 289 return messages.get(key); 290 291 if (messages.contains(name)) 292 return messages.get(name); 293 294 return toUserPresentable(name.toLowerCase()); 295 } 296 297 public static String getLabelForEnum(Messages messages, Enum value) 298 { 299 String prefix = lastTerm(value.getClass().getName()); 300 301 return getLabelForEnum(messages, prefix, value); 302 } 303 304 private static String replace(String input, Pattern pattern, String replacement) 305 { 306 return InternalUtils.replace(input, pattern, replacement); 307 } 308 309 /** 310 * Determines if the two values are equal. They are equal if they are the exact same value (including if they are 311 * both null). Otherwise standard equals() comparison is used. 312 * 313 * @param left value to compare, possibly null 314 * @param right value to compare, possibly null 315 * @return true if same value, both null, or equal 316 */ 317 public static <T> boolean isEqual(T left, T right) 318 { 319 if (left == right) 320 return true; 321 322 if (left == null) 323 return false; 324 325 return left.equals(right); 326 } 327 328 /** 329 * Splits a path at each slash. 330 */ 331 public static String[] splitPath(String path) 332 { 333 return CommonsUtils.splitPath(path); 334 } 335 336 /** 337 * Splits a value around commas. Whitespace around the commas is removed, as is leading and trailing whitespace. 338 * 339 * @since 5.1.0.0 340 */ 341 public static String[] splitAtCommas(String value) 342 { 343 return CommonsUtils.splitAtCommas(value); 344 } 345 346 /** 347 * Copies some content from an input stream to an output stream. It is the caller's responsibility to close the 348 * streams. 349 * 350 * @param in source of data 351 * @param out sink of data 352 * @throws IOException 353 * @since 5.1.0.0 354 */ 355 public static void copy(InputStream in, OutputStream out) throws IOException 356 { 357 byte[] buffer = new byte[BUFFER_SIZE]; 358 359 while (true) 360 { 361 int length = in.read(buffer); 362 363 if (length < 0) 364 break; 365 366 out.write(buffer, 0, length); 367 } 368 369 // TAPESTRY-2415: WebLogic needs this flush() call. 370 out.flush(); 371 } 372 373 public static boolean isEqual(EventContext left, EventContext right) 374 { 375 if (left == right) 376 return true; 377 378 int count = left.getCount(); 379 380 if (count != right.getCount()) 381 return false; 382 383 for (int i = 0; i < count; i++) 384 { 385 if (!left.get(Object.class, i).equals(right.get(Object.class, i))) 386 return false; 387 } 388 389 return true; 390 } 391 392 public static InternalPropertyConduit toInternalPropertyConduit(final PropertyConduit conduit) 393 { 394 if (conduit instanceof InternalPropertyConduit) 395 return (InternalPropertyConduit) conduit; 396 397 return new InternalPropertyConduit() 398 { 399 400 public <T extends Annotation> T getAnnotation(Class<T> annotationClass) 401 { 402 return conduit.getAnnotation(annotationClass); 403 } 404 405 public void set(Object instance, Object value) 406 { 407 conduit.set(instance, value); 408 } 409 410 public Class getPropertyType() 411 { 412 return conduit.getPropertyType(); 413 } 414 415 public Type getPropertyGenericType() 416 { 417 if (conduit instanceof PropertyConduit2) 418 { 419 return ((PropertyConduit2) conduit).getPropertyGenericType(); 420 } 421 return conduit.getPropertyType(); 422 } 423 424 public Object get(Object instance) 425 { 426 return conduit.get(instance); 427 } 428 429 public String getPropertyName() 430 { 431 return null; 432 } 433 }; 434 } 435 436 /** 437 * @param mixinDef the original mixin definition. 438 * @return an Orderable whose id is the mixin name. 439 */ 440 public static Orderable<String> mixinTypeAndOrder(String mixinDef) 441 { 442 int idx = mixinDef.indexOf("::"); 443 if (idx == -1) 444 { 445 return new Orderable<String>(mixinDef, mixinDef); 446 } 447 String type = mixinDef.substring(0, idx); 448 String[] constraints = splitMixinConstraints(mixinDef.substring(idx + 2)); 449 450 return new Orderable<String>(type, type, constraints); 451 } 452 453 public static String[] splitMixinConstraints(String s) 454 { 455 return InternalUtils.isBlank(s) ? null : s.split(";"); 456 } 457 458 /** 459 * Common mapper, used primarily with {@link org.apache.tapestry5.func.Flow#map(org.apache.tapestry5.func.Mapper)} 460 * 461 * @since 5.2.0 462 */ 463 public static Mapper<Asset, StylesheetLink> assetToStylesheetLink = new Mapper<Asset, StylesheetLink>() 464 { 465 public StylesheetLink map(Asset input) 466 { 467 return new StylesheetLink(input); 468 } 469 }; 470 471 public static LinkCreationListener2 toLinkCreationListener2(final LinkCreationListener delegate) 472 { 473 return new LinkCreationListener2() 474 { 475 476 public void createdPageRenderLink(Link link, PageRenderRequestParameters parameters) 477 { 478 delegate.createdPageRenderLink(link); 479 } 480 481 public void createdComponentEventLink(Link link, ComponentEventRequestParameters parameters) 482 { 483 delegate.createdComponentEventLink(link); 484 } 485 }; 486 } 487 488 /** 489 * @since 5.3 490 */ 491 public static String toFileSuffix(String fileName) 492 { 493 int dotx = fileName.lastIndexOf('.'); 494 495 return dotx < 0 ? "" : fileName.substring(dotx + 1); 496 } 497 498 /** 499 * Extracts a value from a map of references. Handles the case where the reference does not exist, 500 * and the case where the reference itself now contains null. 501 * 502 * @since 5.3 503 */ 504 public static <K, V> V getAndDeref(Map<K, ? extends Reference<V>> map, K key) 505 { 506 Reference<V> ref = map.get(key); 507 508 return ref == null ? null : ref.get(); 509 } 510 511 /** 512 * Gathers together an array containing all the threads. 513 * @since 5.4 */ 514 public static Thread[] getAllThreads() { 515 ThreadGroup rootGroup = Thread.currentThread().getThreadGroup(); 516 517 while (true) { 518 ThreadGroup parentGroup = rootGroup.getParent(); 519 if (parentGroup == null) { 520 break; 521 } 522 rootGroup = parentGroup; 523 } 524 525 Thread[] threads = new Thread[rootGroup.activeCount()]; 526 527 while (true) { 528 // A really ugly API. threads.length must be larger than 529 // the actual number of threads, just so we can determine 530 // if we're done. 531 int count = rootGroup.enumerate(threads, true); 532 if (count < threads.length) { 533 return Arrays.copyOf(threads, count); 534 } 535 threads = new Thread[threads.length * 2]; 536 } 537 } 538} 539