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 java.util.*;
020
021/**
022 * A {@link BasicConverter} subclass that supports pluggable type conversion discovery via {@link ConversionFinder}.
023 *
024 * <p>
025 * Pass one or more {@link ConversionFinder} instances to the constructor. Finders are consulted in order before
026 * falling back to the built-in {@link BasicConverter} reflection logic.
027 *
028 * <h5 class='section'>Example:</h5>
029 * <p class='bjava'>
030 *    ConfigurableConverter <jv>converter</jv> = <jk>new</jk> ConfigurableConverter(
031 *       (<jv>in</jv>, <jv>out</jv>) -> <jv>in</jv> == String.<jk>class</jk> &amp;&amp; <jv>out</jv> == MyBean.<jk>class</jk>
032 *          ? (<jv>s</jv>, <jv>memberOf</jv>, <jv>session</jv>, <jv>args</jv>) -> MyBean.fromString((<jk>String</jk>) <jv>s</jv>)
033 *          : <jk>null</jk>
034 *    );
035 *
036 *    MyBean <jv>bean</jv> = <jv>converter</jv>.to(<js>"value"</js>, MyBean.<jk>class</jk>);
037 * </p>
038 *
039 * <h5 class='section'>See Also:</h5><ul>
040 *    <li class='jc'>{@link BasicConverter}
041 *    <li class='jc'>{@link ConversionFinder}
042 *    <li class='jc'>{@link Conversion}
043 * </ul>
044 */
045public class ConfigurableConverter extends BasicConverter {
046
047   private final List<ConversionFinder> finders;
048
049   /**
050    * Constructor.
051    *
052    * @param finders Optional {@link ConversionFinder} instances consulted in order before registered
053    *   type-pair conversions and the built-in {@link BasicConverter} reflection logic.
054    */
055   public ConfigurableConverter(ConversionFinder... finders) {
056      this.finders = List.of(finders);
057   }
058
059   /**
060    * Returns <jk>true</jk> if any registered {@link ConversionFinder} can convert the specified type pair.
061    *
062    * <p>
063    * This only checks the registered finders, not the built-in {@link BasicConverter} reflection logic.
064    *
065    * @param inType The input type class.
066    * @param outType The output type class.
067    * @return <jk>true</jk> if a finder-provided conversion exists for the specified type pair.
068    */
069   public boolean hasCustomConversion(Class<?> inType, Class<?> outType) {
070      for (var finder : finders)
071         if (finder.findConversion(inType, outType) != null)
072            return true;
073      return false;
074   }
075
076   @Override
077   @SuppressWarnings({
078      "unchecked" // Type safety guaranteed by ConversionFinder contract
079   })
080   protected <I,O> Conversion<I,O> findConversion(Class<I> inType, Class<O> outType) {
081      for (var finder : finders) {
082         var fn = (Conversion<I,O>) finder.findConversion(inType, outType);
083         if (fn != null)
084            return fn;
085      }
086      return super.findConversion(inType, outType);
087   }
088}