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.plastic; 014 015import java.lang.reflect.Method; 016import java.util.Objects; 017import java.util.Set; 018import java.util.concurrent.atomic.AtomicLong; 019 020import org.apache.tapestry5.internal.plastic.PlasticInternalUtils; 021import org.apache.tapestry5.internal.plastic.PrimitiveType; 022 023/** 024 * Utilities for user code making use of Plastic. 025 */ 026public class PlasticUtils 027{ 028 private static final String SETTER_METHOD_NAME = "__propertyValueProvider__set"; 029 030 private static final String GETTER_METHOD_NAME = "__propertyValueProvider__get"; 031 032 /** 033 * The {@code toString()} method inherited from Object. 034 */ 035 public static final Method TO_STRING = getMethod(Object.class, "toString"); 036 037 /** 038 * The MethodDescription version of {@code toString()}. 039 */ 040 public static final MethodDescription TO_STRING_DESCRIPTION = new MethodDescription(TO_STRING); 041 042 private static final AtomicLong UID_GENERATOR = new AtomicLong(System.nanoTime()); 043 044 private static final MethodDescription PROPERTY_VALUE_PROVIDER_GETTER_METHOD_DESCRIPTION; 045 046 private static final MethodDescription PROPERTY_VALUE_PROVIDER_SETTER_METHOD_DESCRIPTION; 047 048 private static final MethodDescription FIELD_VALUE_PROVIDER_METHOD_DESCRIPTION; 049 050 static 051 { 052 try { 053 PROPERTY_VALUE_PROVIDER_GETTER_METHOD_DESCRIPTION = new MethodDescription(PropertyValueProvider.class.getMethod(GETTER_METHOD_NAME, String.class)); 054 PROPERTY_VALUE_PROVIDER_SETTER_METHOD_DESCRIPTION = new MethodDescription(PropertyValueProvider.class.getMethod(SETTER_METHOD_NAME, String.class, Object.class)); 055 FIELD_VALUE_PROVIDER_METHOD_DESCRIPTION = new MethodDescription(FieldValueProvider.class.getMethod("__fieldValueProvider__get", String.class)); 056 } catch (Exception e) { 057 throw new ExceptionInInitializerError(e); 058 } 059 } 060 061 062 /** 063 * Returns a string that can be used as part of a Java identifier and is unique 064 * for this JVM. Currently returns a hexadecimal string and initialized by 065 * System.nanoTime() (but both those details may change in the future). 066 * 067 * Note that the returned value may start with a numeric digit, so it should be used as a <em>suffix</em>, not 068 * <em>prefix</em> of a Java identifier. 069 * 070 * @return unique id that can be used as part of a Java identifier 071 */ 072 public static String nextUID() 073 { 074 return Long.toHexString(PlasticUtils.UID_GENERATOR.getAndIncrement()); 075 } 076 077 /** 078 * Converts a type (including array and primitive types) to their type name (the way they are represented in Java 079 * source files). 080 */ 081 public static String toTypeName(Class type) 082 { 083 if (type.isArray()) 084 return toTypeName(type.getComponentType()) + "[]"; 085 086 return type.getName(); 087 } 088 089 /** Converts a number of types (usually, arguments to a method or constructor) into their type names. */ 090 public static String[] toTypeNames(Class[] types) 091 { 092 String[] result = new String[types.length]; 093 094 for (int i = 0; i < result.length; i++) 095 result[i] = toTypeName(types[i]); 096 097 return result; 098 } 099 100 /** 101 * Gets the wrapper type for a given type (if primitive) 102 * 103 * @param type 104 * type to look up 105 * @return the input type for non-primitive type, or corresponding wrapper type (Boolean.class for boolean.class, 106 * etc.) 107 */ 108 public static Class toWrapperType(Class type) 109 { 110 assert type != null; 111 112 return type.isPrimitive() ? PrimitiveType.getByPrimitiveType(type).wrapperType : type; 113 } 114 115 /** 116 * Convenience for getting a method from a class. 117 * 118 * @param declaringClass 119 * containing class 120 * @param name 121 * name of method 122 * @param parameterTypes 123 * types of parameters 124 * @return the Method 125 * @throws RuntimeException 126 * if any error (such as method not found) 127 */ 128 @SuppressWarnings("unchecked") 129 public static Method getMethod(Class declaringClass, String name, Class... parameterTypes) 130 { 131 try 132 { 133 return declaringClass.getMethod(name, parameterTypes); 134 } 135 catch (Exception ex) 136 { 137 throw new RuntimeException(ex); 138 } 139 } 140 141 /** 142 * Uses {@link #getMethod(Class, String, Class...)} and wraps the result as a {@link MethodDescription}. 143 * 144 * @param declaringClass 145 * containing class 146 * @param name 147 * name of method 148 * @param parameterTypes 149 * types of parameters 150 * @return description for method 151 * @throws RuntimeException 152 * if any error (such as method not found) 153 */ 154 public static MethodDescription getMethodDescription(Class declaringClass, String name, Class... parameterTypes) 155 { 156 return new MethodDescription(getMethod(declaringClass, name, parameterTypes)); 157 } 158 159 /** 160 * Determines if the provided type name is a primitive type. 161 * 162 * @param typeName Java type name, such as "boolean" or "java.lang.String" 163 * @return true if primitive 164 */ 165 public static boolean isPrimitive(String typeName) 166 { 167 return PrimitiveType.getByName(typeName) != null; 168 } 169 170 /** 171 * If the given class is an inner class, returns the enclosing class. 172 * Otherwise, returns the class name unchanged. 173 */ 174 public static String getEnclosingClassName(String className) 175 { 176 int index = className.indexOf('$'); 177 return index <= 0 ? className : className.substring(0, index); 178 } 179 180 /** 181 * Utility method for creating {@linkplain FieldInfo} instances. 182 * @param field a {@linkplain PlasticField}. 183 * @return a corresponding {@linkplain FieldInfo}. 184 * @since 5.8.4 185 */ 186 public static FieldInfo toFieldInfo(PlasticField field) 187 { 188 return new FieldInfo(field.getName(), field.getTypeName()); 189 } 190 191 /** 192 * Transforms this {@linkplain PlasticClass} so it implements 193 * {@linkplain FieldValueProvider} for the given set of field names. 194 * Notice attempts to read a superclass' private field will result in 195 * an {@linkplain IllegalAccessError}. 196 * 197 * @param plasticClass a {@linkplain PlasticClass} instance. 198 * @param fieldInfos a {@linkplain Set} of {@linkplain String}s containing the field names. 199 * @since 5.8.4 200 */ 201 public static void implementFieldValueProvider(PlasticClass plasticClass, Set<FieldInfo> fieldInfos) 202 { 203 204 final Set<PlasticMethod> methods = plasticClass.introduceInterface(FieldValueProvider.class); 205 206 if (!methods.isEmpty()) 207 { 208 final PlasticMethod method = methods.iterator().next(); 209 210 method.changeImplementation((builder) -> { 211 212 for (FieldInfo field : fieldInfos) 213 { 214 builder.loadArgument(0); 215 builder.loadConstant(field.name); 216 builder.invokeVirtual(String.class.getName(), "boolean", "equals", Object.class.getName()); 217 builder.when(Condition.NON_ZERO, ifBuilder -> { 218 ifBuilder.loadThis(); 219 ifBuilder.getField(plasticClass.getClassName(), field.name, field.type); 220 ifBuilder.boxPrimitive(field.type); 221 ifBuilder.returnResult(); 222 }); 223 } 224 225 builder.loadThis(); 226 builder.instanceOf(FieldValueProvider.class); 227 228 builder.when(Condition.NON_ZERO, ifBuilder -> { 229 builder.loadThis(); 230 builder.loadArgument(0); 231 ifBuilder.invokeSpecial( 232 plasticClass.getSuperClassName(), 233 FIELD_VALUE_PROVIDER_METHOD_DESCRIPTION); 234 ifBuilder.returnResult(); 235 }); 236 237 builder.throwException(RuntimeException.class, "Field not found or not supported"); 238 239 }); 240 241 } 242 243 } 244 245 /** 246 * Transforms this {@linkplain PlasticClass} so it implements 247 * {@linkplain PropertyValueProvider} for the given set of field names. 248 * The implementation will use the fields' corresponding getters instead 249 * of direct fields access. 250 * 251 * @param plasticClass a {@linkplain PlasticClass} instance. 252 * @param fieldInfos a {@linkplain Set} of {@linkplain String}s containing the filed (i.e. property) names. 253 * @since 5.8.4 254 */ 255 public static void implementPropertyValueProvider(PlasticClass plasticClass, Set<FieldInfo> fieldInfos) 256 { 257 258 final Set<PlasticMethod> methods = plasticClass.introduceInterface(PropertyValueProvider.class); 259 260 final InstructionBuilderCallback getterCallback = (builder) -> { 261 262 for (FieldInfo field : fieldInfos) 263 { 264 builder.loadArgument(0); 265 builder.loadConstant(field.name); 266 builder.invokeVirtual(String.class.getName(), "boolean", "equals", Object.class.getName()); 267 builder.when(Condition.NON_ZERO, ifBuilder -> 268 { 269 final String prefix = field.type.equals("boolean") ? "is" : "get"; 270 final String methodName = prefix + PlasticInternalUtils.capitalize(field.name); 271 272 ifBuilder.loadThis(); 273 builder.invokeVirtual( 274 plasticClass.getClassName(), 275 field.type, 276 methodName); 277 ifBuilder.boxPrimitive(field.type); 278 ifBuilder.returnResult(); 279 }); 280 281 } 282 283 // Field/property not found, so let's try the superclass in case 284 // it also implement 285 286 builder.loadThis(); 287 builder.instanceOf(PropertyValueProvider.class); 288 289 builder.when(Condition.NON_ZERO, ifBuilder -> { 290 builder.loadThis(); 291 builder.loadArgument(0); 292 ifBuilder.invokeSpecial( 293 plasticClass.getSuperClassName(), 294 PROPERTY_VALUE_PROVIDER_GETTER_METHOD_DESCRIPTION); 295 ifBuilder.returnResult(); 296 }); 297 298 // Giving up 299 300 builder.throwException(RuntimeException.class, "Property not found or not supported"); 301 302 }; 303 304 final InstructionBuilderCallback setterCallback = (builder) -> { 305 306 for (FieldInfo field : fieldInfos) 307 { 308 builder.loadArgument(0); 309 builder.loadConstant(field.name); 310 builder.invokeVirtual(String.class.getName(), "boolean", "equals", Object.class.getName()); 311 builder.when(Condition.NON_ZERO, ifBuilder -> 312 { 313 final String methodName = "set" + PlasticInternalUtils.capitalize(field.name); 314 315 ifBuilder.loadThis(); 316 ifBuilder.loadArgument(1); 317 ifBuilder.castOrUnbox(field.type); 318 ifBuilder.invokeVirtual( 319 plasticClass.getClassName(), 320 void.class.getName(), 321 methodName, 322 field.type); 323 ifBuilder.returnResult(); 324 }); 325 326 } 327 328 // Field/property not found, so let's try the superclass in case 329 // it also implement 330 331 builder.loadThis(); 332 builder.instanceOf(PropertyValueProvider.class); 333 334 builder.when(Condition.NON_ZERO, ifBuilder -> { 335 builder.loadThis(); 336 builder.loadArgument(0); 337 builder.loadArgument(1); 338 ifBuilder.invokeSpecial( 339 plasticClass.getSuperClassName(), 340 PROPERTY_VALUE_PROVIDER_SETTER_METHOD_DESCRIPTION); 341 ifBuilder.returnResult(); 342 }); 343 344 // Giving up 345 346 builder.throwException(RuntimeException.class, "Property not found or not supported"); 347 348 }; 349 350 final PlasticMethod getterMethod; 351 final PlasticMethod setterMethod; 352 353 // Superclass has already defined this method, so we need to override it so 354 // it can also find the subclasses' declared fields/properties. 355 if (methods.isEmpty()) 356 { 357 getterMethod = plasticClass.introduceMethod(PROPERTY_VALUE_PROVIDER_GETTER_METHOD_DESCRIPTION, getterCallback); 358 setterMethod = plasticClass.introduceMethod(PROPERTY_VALUE_PROVIDER_SETTER_METHOD_DESCRIPTION, setterCallback); 359 } 360 else 361 { 362 getterMethod = methods.stream() 363 .filter(m -> m.getDescription().methodName.equals(GETTER_METHOD_NAME)) 364 .findFirst() 365 .get(); 366 setterMethod = methods.stream() 367 .filter(m -> m.getDescription().methodName.equals(SETTER_METHOD_NAME)) 368 .findFirst() 369 .get(); 370 } 371 372 getterMethod.changeImplementation(getterCallback); 373 setterMethod.changeImplementation(setterCallback); 374 375 } 376 377 /** 378 * Class used to represent a field name and its type for 379 * {@linkplain PlasticUtils#implementFieldValueProvider(PlasticClass, Set)}. 380 * It shouldn't be used directly. Use {@linkplain PlasticUtils#toFieldInfo(PlasticField)} 381 * instead. 382 * @see PlasticUtils#implementFieldValueProvider(PlasticClass, Set) 383 * @since 5.8.4 384 */ 385 public static class FieldInfo { 386 final private String name; 387 final private String type; 388 public FieldInfo(String name, String type) 389 { 390 super(); 391 this.name = name; 392 this.type = type; 393 } 394 @Override 395 public int hashCode() 396 { 397 return Objects.hash(name); 398 } 399 @Override 400 public boolean equals(Object obj) 401 { 402 if (this == obj) 403 { 404 return true; 405 } 406 if (!(obj instanceof FieldInfo)) 407 { 408 return false; 409 } 410 FieldInfo other = (FieldInfo) obj; 411 return Objects.equals(name, other.name); 412 } 413 @Override 414 public String toString() 415 { 416 return "FieldInfo [name=" + name + ", type=" + type + "]"; 417 } 418 419 } 420 421}