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}