001    // Copyright 2006, 2007, 2008, 2010 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    
015    package org.apache.tapestry5.ioc.internal.services;
016    
017    import java.util.Collection;
018    import java.util.Collections;
019    import java.util.LinkedList;
020    import java.util.List;
021    import java.util.Map;
022    import java.util.Set;
023    import java.util.WeakHashMap;
024    
025    import org.apache.tapestry5.func.F;
026    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
027    import org.apache.tapestry5.ioc.internal.util.InheritanceSearch;
028    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
029    import org.apache.tapestry5.ioc.services.ClassFabUtils;
030    import org.apache.tapestry5.ioc.services.Coercion;
031    import org.apache.tapestry5.ioc.services.CoercionTuple;
032    import org.apache.tapestry5.ioc.services.TypeCoercer;
033    import org.apache.tapestry5.ioc.util.AvailableValues;
034    import org.apache.tapestry5.ioc.util.UnknownValueException;
035    import org.apache.tapestry5.util.StringToEnumCoercion;
036    
037    @SuppressWarnings("all")
038    public class TypeCoercerImpl implements TypeCoercer
039    {
040        // Constructed from the service's configuration.
041    
042        private final Map<Class, List<CoercionTuple>> sourceTypeToTuple = CollectionFactory.newMap();
043    
044        /**
045         * A coercion to a specific target type. Manages a cache of coercions to specific types.
046         */
047        private class TargetCoercion
048        {
049            private final Class type;
050    
051            private final Map<Class, Coercion> cache = CollectionFactory.newConcurrentMap();
052    
053            TargetCoercion(Class type)
054            {
055                this.type = type;
056            }
057    
058            void clearCache()
059            {
060                cache.clear();
061            }
062    
063            Object coerce(Object input)
064            {
065    
066                Class sourceType = input != null ? input.getClass() : void.class;
067    
068                if (type.isAssignableFrom(sourceType))
069                    return input;
070    
071                Coercion c = getCoercion(sourceType);
072    
073                try
074                {
075                    return type.cast(c.coerce(input));
076                }
077                catch (Exception ex)
078                {
079                    throw new RuntimeException(ServiceMessages.failedCoercion(input, type, c, ex), ex);
080                }
081            }
082    
083            String explain(Class sourceType)
084            {
085                return getCoercion(sourceType).toString();
086            }
087    
088            private Coercion getCoercion(Class sourceType)
089            {
090                Coercion c = cache.get(sourceType);
091    
092                if (c == null)
093                {
094                    c = findOrCreateCoercion(sourceType, type);
095                    cache.put(sourceType, c);
096                }
097                return c;
098            }
099        }
100    
101        /**
102         * Map from a target type to a TargetCoercion for that type.
103         */
104        private final Map<Class, TargetCoercion> typeToTargetCoercion = new WeakHashMap<Class, TargetCoercion>();
105    
106        private static final Coercion NO_COERCION = new Coercion<Object, Object>()
107        {
108            public Object coerce(Object input)
109            {
110                return input;
111            }
112        };
113    
114        private static final Coercion COERCION_NULL_TO_OBJECT = new Coercion<Void, Object>()
115        {
116            public Object coerce(Void input)
117            {
118                return null;
119            }
120    
121            @Override
122            public String toString()
123            {
124                return "null --> null";
125            }
126        };
127    
128        public TypeCoercerImpl(Collection<CoercionTuple> tuples)
129        {
130            for (CoercionTuple tuple : tuples)
131            {
132                Class key = tuple.getSourceType();
133    
134                InternalUtils.addToMapList(sourceTypeToTuple, key, tuple);
135            }
136        }
137    
138        @SuppressWarnings("unchecked")
139        public Object coerce(Object input, Class targetType)
140        {
141            assert targetType != null;
142            Class effectiveTargetType = ClassFabUtils.getWrapperType(targetType);
143    
144            if (effectiveTargetType.isInstance(input))
145                return input;
146    
147            return getTargetCoercion(effectiveTargetType).coerce(input);
148        }
149    
150        @SuppressWarnings("unchecked")
151        public <S, T> Coercion<S, T> getCoercion(Class<S> sourceType, Class<T> targetType)
152        {
153            assert sourceType != null;
154            assert targetType != null;
155            Class effectiveSourceType = ClassFabUtils.getWrapperType(sourceType);
156            Class effectiveTargetType = ClassFabUtils.getWrapperType(targetType);
157    
158            if (effectiveTargetType.isAssignableFrom(effectiveSourceType))
159                return NO_COERCION;
160    
161            return getTargetCoercion(effectiveTargetType).getCoercion(effectiveSourceType);
162        }
163    
164        @SuppressWarnings("unchecked")
165        public <S, T> String explain(Class<S> sourceType, Class<T> targetType)
166        {
167            assert sourceType != null;
168            assert targetType != null;
169            Class effectiveTargetType = ClassFabUtils.getWrapperType(targetType);
170            Class effectiveSourceType = ClassFabUtils.getWrapperType(sourceType);
171    
172            // Is a coercion even necessary? Not if the target type is assignable from the
173            // input value.
174    
175            if (effectiveTargetType.isAssignableFrom(effectiveSourceType))
176                return "";
177    
178            return getTargetCoercion(targetType).explain(sourceType);
179        }
180    
181        private synchronized TargetCoercion getTargetCoercion(Class targetType)
182        {
183            TargetCoercion tc = typeToTargetCoercion.get(targetType);
184    
185            if (tc == null)
186            {
187                tc = new TargetCoercion(targetType);
188                typeToTargetCoercion.put(targetType, tc);
189            }
190    
191            return tc;
192        }
193    
194        public synchronized void clearCache()
195        {
196            // There's no need to clear the typeToTargetCoercion map, as it is a WeakHashMap and
197            // will release the keys for classes that are no longer in existence. On the other hand,
198            // there's likely all sorts of references to unloaded classes inside each TargetCoercion's
199            // individual cache, so clear all those.
200    
201            for (TargetCoercion tc : typeToTargetCoercion.values())
202            {
203                // Can tc ever be null?
204    
205                tc.clearCache();
206            }
207        }
208    
209        /**
210         * Here's the real meat; we do a search of the space to find coercions, or a system of
211         * coercions, that accomplish
212         * the desired coercion.
213         * <p/>
214         * There's <strong>TREMENDOUS</strong> room to improve this algorithm. For example, inheritance lists could be
215         * cached. Further, there's probably more ways to early prune the search. However, even with dozens or perhaps
216         * hundreds of tuples, I suspect the search will still grind to a conclusion quickly.
217         * <p/>
218         * The order of operations should help ensure that the most efficient tuple chain is located. If you think about how
219         * tuples are added to the queue, there are two factors: size (the number of steps in the coercion) and
220         * "class distance" (that is, number of steps up the inheritance hiearchy). All the appropriate 1 step coercions
221         * will be considered first, in class distance order. Along the way, we'll queue up all the 2 step coercions, again
222         * in class distance order. By the time we reach some of those, we'll have begun queing up the 3 step coercions, and
223         * so forth, until we run out of input tuples we can use to fabricate multi-step compound coercions, or reach a
224         * final response.
225         * <p/>
226         * This does create a good number of short lived temporary objects (the compound tuples), but that's what the GC is
227         * really good at.
228         * 
229         * @param sourceType
230         * @param targetType
231         * @return coercer from sourceType to targetType
232         */
233        @SuppressWarnings("unchecked")
234        private Coercion findOrCreateCoercion(Class sourceType, Class targetType)
235        {
236            if (sourceType == void.class)
237                return searchForNullCoercion(targetType);
238    
239            // These are instance variables because this method may be called concurrently.
240            // On a true race, we may go to the work of seeking out and/or fabricating
241            // a tuple twice, but it's more likely that different threads are looking
242            // for different source/target coercions.
243    
244            Set<CoercionTuple> consideredTuples = CollectionFactory.newSet();
245            LinkedList<CoercionTuple> queue = CollectionFactory.newLinkedList();
246    
247            seedQueue(sourceType, targetType, consideredTuples, queue);
248    
249            while (!queue.isEmpty())
250            {
251                CoercionTuple tuple = queue.removeFirst();
252    
253                // If the tuple results in a value type that is assignable to the desired target type,
254                // we're done! Later, we may add a concept of "cost" (i.e. number of steps) or
255                // "quality" (how close is the tuple target type to the desired target type). Cost
256                // is currently implicit, as compound tuples are stored deeper in the queue,
257                // so simpler coercions will be located earlier.
258    
259                Class tupleTargetType = tuple.getTargetType();
260    
261                if (targetType.isAssignableFrom(tupleTargetType))
262                    return tuple.getCoercion();
263    
264                // So .. this tuple doesn't get us directly to the target type.
265                // However, it *may* get us part of the way. Each of these
266                // represents a coercion from the source type to an intermediate type.
267                // Now we're going to look for conversions from the intermediate type
268                // to some other type.
269    
270                queueIntermediates(sourceType, targetType, tuple, consideredTuples, queue);
271            }
272    
273            // Not found anywhere. Identify the source and target type and a (sorted) list of
274            // all the known coercions.
275    
276            throw new UnknownValueException(String.format("Could not find a coercion from type %s to type %s.",
277                    sourceType.getName(), targetType.getName()), buildCoercionCatalog());
278        }
279    
280        /**
281         * Coercion from null is special; we match based on the target type and its not a spanning
282         * search. In many cases, we
283         * return a pass-thru that leaves the value as null.
284         * 
285         * @param targetType
286         *            desired type
287         * @return the coercion
288         */
289        private Coercion searchForNullCoercion(Class targetType)
290        {
291            List<CoercionTuple> tuples = getTuples(void.class, targetType);
292    
293            for (CoercionTuple tuple : tuples)
294            {
295                Class tupleTargetType = tuple.getTargetType();
296    
297                if (targetType.equals(tupleTargetType))
298                    return tuple.getCoercion();
299            }
300    
301            // Typical case: no match, this coercion passes the null through
302            // as null.
303    
304            return COERCION_NULL_TO_OBJECT;
305        }
306    
307        /**
308         * Builds a string listing all the coercions configured for the type coercer, sorted
309         * alphabetically.
310         */
311        @SuppressWarnings("unchecked")
312        private AvailableValues buildCoercionCatalog()
313        {
314            List<CoercionTuple> masterList = CollectionFactory.newList();
315    
316            for (List<CoercionTuple> list : sourceTypeToTuple.values())
317            {
318                masterList.addAll(list);
319            }
320    
321            return new AvailableValues("Configured coercions", masterList);
322        }
323    
324        /**
325         * Seeds the pool with the initial set of coercions for the given type.
326         */
327        private void seedQueue(Class sourceType, Class targetType, Set<CoercionTuple> consideredTuples,
328                LinkedList<CoercionTuple> queue)
329        {
330            // Work from the source type up looking for tuples
331    
332            for (Class c : new InheritanceSearch(sourceType))
333            {
334                List<CoercionTuple> tuples = getTuples(c, targetType);
335    
336                if (tuples == null)
337                    continue;
338    
339                for (CoercionTuple tuple : tuples)
340                {
341                    queue.addLast(tuple);
342                    consideredTuples.add(tuple);
343                }
344    
345                // Don't pull in Object -> type coercions when doing
346                // a search from null.
347    
348                if (sourceType == void.class)
349                    return;
350            }
351        }
352    
353        /**
354         * Creates and adds to the pool a new set of coercions based on an intermediate tuple. Adds
355         * compound coercion tuples
356         * to the end of the queue.
357         * 
358         * @param sourceType
359         *            the source type of the coercion
360         * @param targetType
361         *            TODO
362         * @param intermediateTuple
363         *            a tuple that converts from the source type to some intermediate type (that is not
364         *            assignable to the target type)
365         * @param consideredTuples
366         *            set of tuples that have already been added to the pool (directly, or as a compound
367         *            coercion)
368         * @param queue
369         *            the work queue of tuples
370         */
371        @SuppressWarnings("unchecked")
372        private void queueIntermediates(Class sourceType, Class targetType, CoercionTuple intermediateTuple,
373                Set<CoercionTuple> consideredTuples, LinkedList<CoercionTuple> queue)
374        {
375            Class intermediateType = intermediateTuple.getTargetType();
376    
377            for (Class c : new InheritanceSearch(intermediateType))
378            {
379                for (CoercionTuple tuple : getTuples(c, targetType))
380                {
381                    if (consideredTuples.contains(tuple))
382                        continue;
383    
384                    Class newIntermediateType = tuple.getTargetType();
385    
386                    // If this tuple is for coercing from an intermediate type back towards our
387                    // initial source type, then ignore it. This should only be an optimization,
388                    // as branches that loop back towards the source type will
389                    // eventually be considered and discarded.
390    
391                    if (sourceType.isAssignableFrom(newIntermediateType))
392                        continue;
393    
394                    // The intermediateTuple coercer gets from S --> I1 (an intermediate type).
395                    // The current tuple's coercer gets us from I2 --> X. where I2 is assignable
396                    // from I1 (i.e., I2 is a superclass/superinterface of I1) and X is a new
397                    // intermediate type, hopefully closer to our eventual target type.
398    
399                    Coercion compoundCoercer = new CompoundCoercion(intermediateTuple.getCoercion(), tuple.getCoercion());
400    
401                    CoercionTuple compoundTuple = new CoercionTuple(sourceType, newIntermediateType, compoundCoercer, false);
402    
403                    // So, every tuple that is added to the queue can take as input the sourceType.
404                    // The target type may be another intermediate type, or may be something
405                    // assignable to the target type, which will bring the search to a successful
406                    // conclusion.
407    
408                    queue.addLast(compoundTuple);
409                    consideredTuples.add(tuple);
410                }
411            }
412        }
413    
414        /**
415         * Returns a non-null list of the tuples from the source type.
416         * 
417         * @param sourceType
418         *            used to locate tuples
419         * @param targetType
420         *            used to add synthetic tuples
421         * @return non-null list of tuples
422         */
423        private List<CoercionTuple> getTuples(Class sourceType, Class targetType)
424        {
425            List<CoercionTuple> tuples = sourceTypeToTuple.get(sourceType);
426    
427            if (tuples == null)
428                tuples = Collections.emptyList();
429    
430            // So, when we see String and an Enum type, we add an additional synthetic tuple to the end
431            // of the real list. This is the easiest way to accomplish this is a thread-safe and class-reloading
432            // safe way (i.e., what if the Enum is defined by a class loader that gets discarded?  Don't want to cause
433            // memory leaks by retaining an instance). In any case, there are edge cases where we may create
434            // the tuple unnecessarily (such as when an explicit string-to-enum coercion is part of the TypeCoercer
435            // configuration), but on the whole, this is cheap at works.
436            
437            if (sourceType == String.class && Enum.class.isAssignableFrom(targetType))
438                tuples = extend(tuples, new CoercionTuple(sourceType, targetType, new StringToEnumCoercion(targetType)));
439    
440            return tuples;
441        }
442    
443        private static <T> List<T> extend(List<T> list, T extraValue)
444        {
445            return F.flow(list).append(extraValue).toList();
446        }
447    }