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