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