    001// Copyright 2006, 2007, 2008, 2010, 2011, 2012 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.
    014
    015package org.apache.tapestry5.commons.services;
    016
    017import org.apache.tapestry5.commons.MappedConfiguration;
    018import org.apache.tapestry5.plastic.PlasticUtils;
    019
    020/**
    021 * An immutable object that represents a mapping from one type to another. This is also the contribution type when
    022 * building the {@link org.apache.tapestry5.commons.services.TypeCoercer} service. Wraps a
    023 * {@link org.apache.tapestry5.commons.services.Coercion} object that performs the work with additional properties that
    024 * describe
    025 * the input and output types of the coercion, needed when searching for an appropriate coercion (or sequence of
    026 * coercions).
    027 *
    028 * @param <S>
    029 *         source (input) type
    030 * @param <T>
    031 *         target (output) type
    032 */
    033public final class CoercionTuple<S, T>
    034{
    035    private final Class<S> sourceType;
    036
    037    private final Class<T> targetType;
    038
    039    private final Coercion<S, T> coercion;
    040
    041    private final Key key;
    042
    043    /**
    044     * Wraps an arbitrary coercion with an implementation of toString() that identifies the source and target types.
    045     */
    046    private class CoercionWrapper<WS, WT> implements Coercion<WS, WT>
    047    {
    048        private final Coercion<WS, WT> coercion;
    049
    050        public CoercionWrapper(Coercion<WS, WT> coercion)
    051        {
    052            this.coercion = coercion;
    053        }
    054
    055        @Override
    056        public WT coerce(WS input)
    057        {
    058            return coercion.coerce(input);
    059        }
    060
    061        @Override
    062        public String toString()
    063        {
    064            return String.format("%s --> %s", convert(sourceType), convert(targetType));
    065        }
    066    }
    067
    068    private String convert(Class type)
    069    {
    070        if (Void.class.equals(type))
    071            return "null";
    072
    073        String name = PlasticUtils.toTypeName(type);
    074
    075        int dotx = name.lastIndexOf('.');
    076
    077        // Strip off a package name of "java.lang"
    078
    079        if (dotx > 0 && name.substring(0, dotx).equals("java.lang"))
    080            return name.substring(dotx + 1);
    081
    082        return name;
    083    }
    084
    085    /**
    086     * Standard constructor, which defaults wrap to true.
    087     */
    088    public CoercionTuple(Class<S> sourceType, Class<T> targetType, Coercion<S, T> coercion)
    089    {
    090        this(sourceType, targetType, coercion, true);
    091    }
    092
    093    /**
    094     * Convenience method to create a coercion tuple using {@linkplain #create(Class, Class, Coercion)}
    095     * and add it to a {@linkplain MappedConfiguration} in a single step.
    096     *
    097     * @since 5.8.0
    098     */
    099    @SuppressWarnings("rawtypes")
    100    public static <S, T> void add(
    101            MappedConfiguration<CoercionTuple.Key, CoercionTuple> configuration,
    102            Class<S> sourceType, Class<T> targetType, Coercion<S, T> coercion)
    103    {
    104        CoercionTuple<S, T> coercionTuple = new CoercionTuple<S, T>(sourceType, targetType, coercion);
    105        configuration.add(coercionTuple.getKey(), coercionTuple);
    106    }
    107
    108    /**
    109     * Convenience method to create a coercion tuple using {@linkplain #create(Class, Class, Coercion)}
    110     * and override a matching one in a {@linkplain MappedConfiguration} in a single step.
    111     *
    112     * @since 5.8.0
    113     */
    114    @SuppressWarnings("rawtypes")
    115    public static <S, T> void override(
    116            MappedConfiguration<CoercionTuple.Key, CoercionTuple> configuration,
    117            Class<S> sourceType, Class<T> targetType, Coercion<S, T> coercion)
    118    {
    119        CoercionTuple<S, T> coercionTuple = new CoercionTuple<S, T>(sourceType, targetType, coercion);
    120        configuration.override(coercionTuple.getKey(), coercionTuple);
    121    }
    122
    123    /**
    124     * Convenience constructor to help with generics.
    125     *
    126     * @since 5.2.0
    127     */
    128    public static <S, T> CoercionTuple<S, T> create(Class<S> sourceType, Class<T> targetType, Coercion<S, T> coercion)
    129    {
    130        return new CoercionTuple<S, T>(sourceType, targetType, coercion);
    131    }
    132
    133    /**
    134     * Internal-use constructor.
    135     *
    136     * @param sourceType
    137     *         the source (or input) type of the coercion, may be Void.class to indicate a coercion from null
    138     * @param targetType
    139     *         the target (or output) type of the coercion
    140     * @param coercion
    141     *         the object that performs the coercion
    142     * @param wrap
    143     *         if true, the coercion is wrapped to provide a useful toString()
    144     */
    145    @SuppressWarnings("unchecked")
    146    public CoercionTuple(Class<S> sourceType, Class<T> targetType, Coercion<S, T> coercion, boolean wrap)
    147    {
    148        assert sourceType != null;
    149        assert targetType != null;
    150        assert coercion != null;
    151
    152        this.sourceType = PlasticUtils.toWrapperType(sourceType);
    153        this.targetType = PlasticUtils.toWrapperType(targetType);
    154        this.coercion = wrap ? new CoercionWrapper<S, T>(coercion) : coercion;
    155        this.key = new Key();
    156    }
    157
    158    @Override
    159    public String toString()
    160    {
    161        return coercion.toString();
    162    }
    163
    164    public Coercion<S, T> getCoercion()
    165    {
    166        return coercion;
    167    }
    168
    169    public Class<S> getSourceType()
    170    {
    171        return sourceType;
    172    }
    173
    174    public Class<T> getTargetType()
    175    {
    176        return targetType;
    177    }
    178
    179    public Key getKey()
    180    {
    181        return key;
    182    }
    183
    184    /**
    185     * Class that represents the key to be used to the mapped configuration of the
    186     * {@link TypeCoercer} service.
    187     */
    188    public final class Key
    189    {
    190
    191        protected Class<S> getSourceType()
    192        {
    193            return sourceType;
    194        }
    195
    196        protected Class<T> getTargetType()
    197        {
    198            return targetType;
    199        }
    200
    201        @Override
    202        public String toString() {
    203            return String.format("%s -> %s", sourceType.getName(), targetType.getName());
    204        }
    205
    206        @Override
    207        public int hashCode()
    208        {
    209            final int prime = 31;
    210            int result = 1;
    211            result = prime * result + ((sourceType == null) ? 0 : sourceType.hashCode());
    212            result = prime * result + ((targetType == null) ? 0 : targetType.hashCode());
    213            return result;
    214        }
    215
    216        @Override
    217        public boolean equals(Object obj)
    218        {
    219            if (this == obj)
    220                return true;
    221            if (obj == null)
    222                return false;
    223            if (getClass() != obj.getClass())
    224                return false;
    225
    226            Key other = (Key) obj;
    227            if (sourceType == null)
    228            {
    229                if (other.getSourceType() != null)
    230                    return false;
    231            } else if (!sourceType.equals(other.getSourceType()))
    232                return false;
    233            if (targetType == null)
    234            {
    235                if (other.getTargetType() != null)
    236                    return false;
    237            } else if (!targetType.equals(other.getTargetType()))
    238                return false;
    239            return true;
    240        }
    241
    242    }
    243
    244}