    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}