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.getAllParentsChildFirst()) { 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}