001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.commons.conversion;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.lang.reflect.*;
022import java.util.*;
023import java.util.concurrent.*;
024import java.util.stream.*;
025
026/**
027 * An abstract {@link Converter} implementation that caches previously determined type conversions.
028 *
029 * <p>
030 * Conversion functions are discovered lazily on the first call for a given input/output type pair and
031 * stored in a two-level {@link ConcurrentHashMap} for subsequent fast lookup.
032 *
033 * <p>
034 * Subclasses implement {@link #findConversion(Class, Class)} to provide the actual conversion logic.
035 * The result is cached so that reflection or other expensive discovery happens only once per type pair.
036 *
037 * <h5 class='section'>Example:</h5>
038 * <p class='bjava'>
039 *    <jk>public class</jk> MyConverter <jk>extends</jk> CachingConverter {
040 *
041 *       <ja>@Override</ja>
042 *       <jk>protected</jk> &lt;I,O&gt; Conversion&lt;I,O&gt; findConversion(Class&lt;I&gt; inType, Class&lt;O&gt; outType) {
043 *          <jk>if</jk> (inType == String.<jk>class</jk> &amp;&amp; outType == Integer.<jk>class</jk>)
044 *             <jk>return</jk> (Conversion&lt;I,O&gt;) (Conversion&lt;String,Integer&gt;) (<jv>s</jv>, <jv>memberOf</jv>, <jv>session</jv>, <jv>args</jv>) -&gt; Integer.<jsm>valueOf</jsm>(<jv>s</jv>);
045 *          <jk>return null</jk>;
046 *       }
047 *    }
048 * </p>
049 *
050 * <h5 class='section'>Thread Safety:</h5>
051 * <p>
052 * This class is thread-safe. The cache uses {@link ConcurrentHashMap} for safe concurrent access.
053 * </p>
054 */
055@SuppressWarnings({
056   "unchecked" // Cast is safe: type parameter is verified at construction.
057})
058public abstract class CachingConverter implements Converter {
059
060   // Sentinel stored in the cache when findConversion() returns null.
061   // ConcurrentHashMap does not permit null values, so we cannot store null directly.
062   // Using this sentinel avoids re-invoking findConversion() for unconvertable type pairs.
063   private static final Conversion<?,?> NO_CONVERSION = (in, memberOf, session, args) -> null; // HTT: sentinel body is never invoked; ConcurrentHashMap prohibits null values so we use this placeholder
064
065   /**
066    * Returns the appropriate default value for a null input to the given target type.
067    *
068    * <ul>
069    *    <li>For {@link Optional}: returns {@link Optional#empty()}, or {@code Optional.of(Optional.empty())} for
070    *       nested optional types such as {@code Optional<Optional<Integer>>}.
071    *    <li>For primitives: returns the JVM zero/false default (e.g. {@code 0} for {@code int}).
072    *    <li>For all other types: returns {@code null}.
073    * </ul>
074    *
075    * @param type The target type.
076    * @param args The generic type arguments (e.g. element type for {@link Optional}).
077    * @param <T> The target type.
078    * @return The appropriate null default.
079    */
080   static <T> T nullDefault(Class<T> type, Class<?>... args) {
081      if (type == Optional.class) {
082         if (args.length > 0)
083            return (T) o(nullDefault(args[0], Arrays.copyOfRange(args, 1, args.length)));
084         return (T) oe();
085      }
086      if (type == OptionalInt.class)
087         return (T) OptionalInt.empty();
088      if (type == OptionalLong.class)
089         return (T) OptionalLong.empty();
090      if (type == OptionalDouble.class)
091         return (T) OptionalDouble.empty();
092      return type.isPrimitive() ? primitiveDefault(type) : null;
093   }
094
095   /**
096    * Returns the JVM default value for a primitive type, or <jk>null</jk> if the type is not primitive.
097    *
098    * <p>
099    * This is used to satisfy {@link #to} when the input is <jk>null</jk> and the target type is a
100    * non-boxed primitive (e.g. {@code int.class}), where returning <jk>null</jk> would be invalid.
101    *
102    * @param type The target type.
103    * @param <T> The target type.
104    * @return The JVM zero/false default, or <jk>null</jk> if {@code type} is not a primitive.
105    */
106   static <T> T primitiveDefault(Class<T> type) {
107      if (type == Integer.TYPE)   return (T) Integer.valueOf(0);
108      if (type == Long.TYPE)      return (T) Long.valueOf(0L);
109      if (type == Double.TYPE)    return (T) Double.valueOf(0.0d);
110      if (type == Float.TYPE)     return (T) Float.valueOf(0.0f);
111      if (type == Boolean.TYPE)   return (T) Boolean.FALSE;
112      if (type == Short.TYPE)     return (T) Short.valueOf((short)0);
113      if (type == Byte.TYPE)      return (T) Byte.valueOf((byte)0);
114      if (type == Character.TYPE) return (T) Character.valueOf('\0');
115      return null; // HTT: no other primitive types exist in Java
116   }
117
118   // Two-level cache: input type -> output type -> conversion function (or NO_CONVERSION sentinel).
119   private final Map<Class<?>,Map<Class<?>,Conversion<?,?>>> conversions = new ConcurrentHashMap<>();
120
121   /**
122    * Finds a conversion function from the specified input type to the specified output type.
123    *
124    * <p>
125    * This method is called lazily the first time a conversion between a given type pair is requested.
126    * The result is cached so subsequent calls for the same type pair bypass this method entirely.
127    * When this method returns <jk>null</jk>, a sentinel is stored so that unconvertable type pairs
128    * are also only evaluated once.
129    *
130    * <p>
131    * Implementations should return <jk>null</jk> if no conversion is possible for the given types,
132    * which will cause {@link #to(Object, Class)} and {@link #to(Object, Type, Type...)} to throw
133    * {@link InvalidConversionException} for that type pair.
134    *
135    * @param <I> The input type.
136    * @param <O> The output type.
137    * @param inType The runtime class of the input object.
138    * @param outType The target output class.
139    * @return A {@link Conversion} function, or <jk>null</jk> if no conversion is available.
140    */
141   protected abstract <I,O> Conversion<I,O> findConversion(Class<I> inType, Class<O> outType);
142
143   private <I,O> Conversion<I,O> lookupConversion(Class<I> inType, Class<O> outType) {
144      var fn = conversions
145         .computeIfAbsent(inType, k -> new ConcurrentHashMap<>())
146         .computeIfAbsent(outType, k -> {
147            Conversion<I,O> found = findConversion(inType, outType);
148            return found != null ? found : NO_CONVERSION;
149         });
150      return fn == NO_CONVERSION ? null : (Conversion<I,O>) fn;
151   }
152
153   @Override
154   public boolean canConvert(Class<?> inType, Class<?> outType) {
155      if (inType == outType)
156         return true;
157      return lookupConversion(inType, outType) != null;
158   }
159
160   /**
161    * Converts the specified object to the specified type.
162    *
163    * <p>
164    * On the first call for a given input/output type pair, {@link #findConversion(Class, Class)} is
165    * invoked and the result is cached. Subsequent calls use the cached function directly.
166    *
167    * @param o The object to convert. Can be <jk>null</jk>.
168    * @param type The target type.
169    * @param <T> The target type.
170    * @return The converted object, or the primitive zero-value if the input is <jk>null</jk> and the target
171    *    is a primitive type, or <jk>null</jk> otherwise.
172    * @throws InvalidConversionException If no conversion path exists from the input type to the target type.
173    */
174   @Override
175   public <T> T to(Object o, Class<T> type) {
176      if (o == null)
177         return type.isPrimitive() ? primitiveDefault(type) : null;
178      var inType = o.getClass();
179      if (inType == type)
180         return (T) o;
181      var fn = (Conversion<Object,T>) lookupConversion(inType, type);
182      if (fn == null)
183         throw new InvalidConversionException(inType, type);
184      return fn.to(o, null, (ConverterSession)null);
185   }
186
187   /**
188    * Converts the specified object to the specified parameterized type.
189    *
190    * <p>
191    * The raw class is extracted from {@code mainType} (supporting both {@link Class} and
192    * {@link ParameterizedType} values). Type arguments are extracted from {@code args} the same way
193    * and passed through to the cached {@link Conversion} function at call time.
194    *
195    * <p>
196    * The cache is keyed on the raw input and output classes only. The {@code args} are not part of
197    * the cache key — a single cached {@link Conversion} handles all parameterizations of a given
198    * output type.
199    *
200    * @param o The object to convert. Can be <jk>null</jk>.
201    * @param mainType The target type. May be a {@link Class} or {@link ParameterizedType}.
202    * @param args The type arguments of the target type (e.g. element type for collections).
203    * @param <T> The target type.
204    * @return The converted object, or <jk>null</jk> if the input is <jk>null</jk>.
205    * @throws InvalidConversionException If no conversion path exists from the input type to the target type.
206    */
207   @Override
208   public <T> T to(Object o, Type mainType, Type... args) {
209      var rawType = (Class<T>) (mainType instanceof ParameterizedType mainType2 ? mainType2.getRawType() : (Class<?>) mainType);
210      var argClasses = Stream.of(args)
211         .map(t -> (Class<?>) (t instanceof ParameterizedType t2 ? t2.getRawType() : t))
212         .toArray(Class[]::new);
213      if (o == null)
214         return nullDefault(rawType, argClasses);
215      var inType = o.getClass();
216      var fn = (Conversion<Object,T>) lookupConversion(inType, rawType);
217      if (fn == null)
218         throw new InvalidConversionException(inType, rawType);
219      return fn.to(o, null, (ConverterSession)null, argClasses);
220   }
221
222   /**
223    * Converts the specified object to the specified type, using the given outer instance and converter session.
224    *
225    * <p>
226    * The session is forwarded to the cached {@link Conversion} function so conversion lambdas can access
227    * contextual objects such as {@link TimeZone} or {@link Locale}.
228    *
229    * @param o The object to convert. Can be <jk>null</jk>.
230    * @param memberOf The outer instance for non-static inner class construction, or <jk>null</jk>.
231    * @param session The converter session providing contextual objects, or <jk>null</jk>.
232    * @param type The target type.
233    * @param <T> The target type.
234    * @return The converted object, or the primitive zero-value if the input is <jk>null</jk> and the target
235    *    is a primitive type, or <jk>null</jk> otherwise.
236    * @throws InvalidConversionException If no conversion path exists from the input type to the target type.
237    */
238   @Override
239   public <T> T to(Object o, Object memberOf, ConverterSession session, Class<T> type) {
240      if (o == null)
241         return nullDefault(type);
242      var inType = o.getClass();
243      if (inType == type)
244         return (T) o;
245      var fn = (Conversion<Object,T>) lookupConversion(inType, type);
246      if (fn == null)
247         throw new InvalidConversionException(inType, type);
248      return fn.to(o, memberOf, session);
249   }
250
251   /**
252    * Converts the specified object to the specified parameterized type, using the given outer instance and converter session.
253    *
254    * <p>
255    * The session is forwarded to the cached {@link Conversion} function so conversion lambdas can access
256    * contextual objects such as {@link TimeZone} or {@link Locale}.
257    *
258    * @param o The object to convert. Can be <jk>null</jk>.
259    * @param memberOf The outer instance for non-static inner class construction, or <jk>null</jk>.
260    * @param session The converter session providing contextual objects, or <jk>null</jk>.
261    * @param mainType The target type. May be a {@link Class} or {@link ParameterizedType}.
262    * @param args The type arguments of the target type (e.g. element type for collections).
263    * @param <T> The target type.
264    * @return The converted object, or <jk>null</jk> if the input is <jk>null</jk>.
265    * @throws InvalidConversionException If no conversion path exists from the input type to the target type.
266    */
267   @Override
268   public <T> T to(Object o, Object memberOf, ConverterSession session, Type mainType, Type... args) {
269      var rawType = (Class<T>) (mainType instanceof ParameterizedType mainType2 ? mainType2.getRawType() : (Class<?>) mainType);
270      var argClasses = Stream.of(args)
271         .map(t -> (Class<?>) (t instanceof ParameterizedType t2 ? t2.getRawType() : t))
272         .toArray(Class[]::new);
273      if (o == null)
274         return nullDefault(rawType, argClasses);
275      var inType = o.getClass();
276      var fn = (Conversion<Object,T>) lookupConversion(inType, rawType);
277      if (fn == null)
278         throw new InvalidConversionException(inType, rawType);
279      var result = fn.to(o, memberOf, session, argClasses);
280      if (result == null && rawType.isPrimitive())
281         return primitiveDefault(rawType);
282      return result;
283   }
284}