001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  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 distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.reflect;
014
015import static org.apache.juneau.internal.ClassUtils.*;
016import static org.apache.juneau.reflect.ReflectFlags.*;
017
018import java.util.concurrent.*;
019
020import java.lang.reflect.*;
021import java.util.*;
022
023/**
024 * Cache of object that convert POJOs to and from common types such as strings, readers, and input streams.
025 */
026public class Mutaters {
027   private static final ConcurrentHashMap<Class<?>,Map<Class<?>,Mutater<?,?>>> CACHE = new ConcurrentHashMap<>();
028
029   /**
030    * Represents a non-existent transform.
031    */
032   public static final Mutater<Object,Object> NULL = new Mutater<Object,Object>() {
033      @Override
034      public Object mutate(Object outer, Object in) {
035         return null;
036      }
037   };
038
039   // Special cases.
040   static {
041
042      // TimeZone doesn't follow any standard conventions.
043      add(String.class, TimeZone.class,
044         new Mutater<String,TimeZone>() {
045            @Override public TimeZone mutate(Object outer, String in) {
046               return TimeZone.getTimeZone(in);
047            }
048         }
049      );
050      add(TimeZone.class, String.class,
051         new Mutater<TimeZone,String>() {
052            @Override public String mutate(Object outer, TimeZone in) {
053               return in.getID();
054            }
055         }
056      );
057
058      // Locale(String) doesn't work on strings like "ja_JP".
059      add(String.class, Locale.class,
060         new Mutater<String,Locale>() {
061            @Override
062            public Locale mutate(Object outer, String in) {
063               return Locale.forLanguageTag(in.replace('_', '-'));
064            }
065         }
066      );
067
068      // String-to-Boolean transform should allow for "null" keyword.
069      add(String.class, Boolean.class,
070         new Mutater<String,Boolean>() {
071            @Override
072            public Boolean mutate(Object outer, String in) {
073               if (in == null || "null".equals(in) || in.isEmpty())
074                  return null;
075               return Boolean.valueOf(in);
076            }
077         }
078      );
079   }
080
081   /**
082    * Adds a transform for the specified input/output types.
083    *
084    * @param ic The input type.
085    * @param oc The output type.
086    * @param t The transform for converting the input to the output.
087    */
088   public static synchronized void add(Class<?> ic, Class<?> oc, Mutater<?,?> t) {
089      Map<Class<?>,Mutater<?,?>> m = CACHE.get(oc);
090      if (m == null) {
091         m = new ConcurrentHashMap<>();
092         CACHE.put(oc, m);
093      }
094      m.put(ic, t);
095   }
096
097   /**
098    * Returns the transform for converting the specified input type to the specified output type.
099    *
100    * @param ic The input type.
101    * @param oc The output type.
102    * @return The transform for performing the conversion, or <jk>null</jk> if the conversion cannot be made.
103    */
104   @SuppressWarnings({ "unchecked", "rawtypes" })
105   public static <I,O> Mutater<I,O> get(Class<I> ic, Class<O> oc) {
106
107      if (ic == null || oc == null)
108         return null;
109
110      Map<Class<?>,Mutater<?,?>> m = CACHE.get(oc);
111      if (m == null) {
112         m = new ConcurrentHashMap<>();
113         CACHE.putIfAbsent(oc, m);
114         m = CACHE.get(oc);
115      }
116
117      Mutater t = m.get(ic);
118
119      if (t == null) {
120         t = find(ic, oc, m);
121         m.put(ic, t);
122      }
123
124      return t == NULL ? null : t;
125   }
126
127   /**
128    * Returns the transform for converting the specified input type to the specified output type.
129    *
130    * @param ic The input type.
131    * @param oc The output type.
132    * @return The transform for performing the conversion, or <jk>null</jk> if the conversion cannot be made.
133    */
134   public static <I,O> boolean hasMutate(Class<I> ic, Class<O> oc) {
135      return get(ic, oc) != NULL;
136   }
137
138   @SuppressWarnings({"unchecked","rawtypes"})
139   private static Mutater find(Class<?> ic, Class<?> oc, Map<Class<?>,Mutater<?,?>> m) {
140
141      if (ic == oc) {
142         return new Mutater() {
143            @Override public Object mutate(Object outer, Object in) {
144               return in;
145            }
146         };
147      }
148
149      ClassInfo ici = getClassInfo(ic), oci = getClassInfo(oc);
150
151      for (ClassInfo pic : ici.getAllParents()) {
152         Mutater t = m.get(pic.inner());
153         if (t != null)
154            return t;
155      }
156
157      if (ic == String.class) {
158         Class<?> oc2 = oci.hasPrimitiveWrapper() ? oci.getPrimitiveWrapper() : oc;
159         ClassInfo oc2i = getClassInfo(oc2);
160
161         final MethodInfo createMethod = oc2i.getStaticCreateMethod(ic, "forName");
162
163         if (oc2.isEnum() && createMethod == null) {
164            return new Mutater<String,Object>() {
165               @Override
166               public Object mutate(Object outer, String in) {
167                  return Enum.valueOf((Class<? extends Enum>)oc2, in);
168               }
169            };
170         }
171
172         if (createMethod != null) {
173            return new Mutater<String,Object>() {
174               @Override
175               public Object mutate(Object outer, String in) {
176                  try {
177                     return createMethod.invoke(null, in);
178                  } catch (Exception e) {
179                     throw new RuntimeException(e);
180                  }
181               }
182            };
183         }
184      } else {
185         MethodInfo createMethod = oci.getStaticCreateMethod(ic);
186         if (createMethod != null) {
187            Method cm = createMethod.inner();
188            return new Mutater() {
189               @Override
190               public Object mutate(Object context, Object in) {
191                  try {
192                     return cm.invoke(null, in);
193                  } catch (Exception e) {
194                     throw new RuntimeException(e);
195                  }
196               }
197            };
198         }
199      }
200
201      ConstructorInfo c = oci.getPublicConstructor(ic);
202      if (c != null && c.isNotDeprecated()) {
203         boolean isMemberClass = oci.isNonStaticMemberClass();
204         return new Mutater() {
205            @Override
206            public Object mutate(Object outer, Object in) {
207               try {
208                  if (isMemberClass)
209                     return c.invoke(outer, in);
210                  return c.invoke(in);
211               } catch (Exception e) {
212                  throw new RuntimeException(e);
213               }
214            }
215         };
216      }
217
218      MethodInfo toXMethod = findToXMethod(ici, oci);
219      if (toXMethod != null) {
220         return new Mutater() {
221            @Override
222            public Object mutate(Object outer, Object in) {
223               try {
224                  return toXMethod.invoke(in);
225               } catch (Exception e) {
226                  throw new RuntimeException(e);
227               }
228            }
229         };
230      }
231
232      return NULL;
233   }
234
235   private static MethodInfo findToXMethod(ClassInfo ic, ClassInfo oc) {
236      String tn = oc.getReadableName();
237      for (MethodInfo m : ic.getAllMethods()) {
238         if (m.isAll(PUBLIC, NOT_STATIC, HAS_NO_PARAMS, NOT_DEPRECATED)
239            && m.getSimpleName().startsWith("to")
240            && m.getSimpleName().substring(2).equalsIgnoreCase(tn))
241            return m;
242      }
243      return null;
244   }
245
246}