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