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