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.internal; 014 015import java.util.concurrent.*; 016 017import static org.apache.juneau.internal.ClassUtils.*; 018import static org.apache.juneau.internal.ClassFlags.*; 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 TransformCache { 027 private static final ConcurrentHashMap<Class<?>,Map<Class<?>,Transform<?,?>>> CACHE = new ConcurrentHashMap<>(); 028 029 /** 030 * Represents a non-existent transform. 031 */ 032 public static final Transform<Object,Object> NULL = new Transform<Object,Object>() { 033 @Override 034 public Object transform(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 Transform<String,TimeZone>() { 045 @Override public TimeZone transform(Object outer, String in) { 046 return TimeZone.getTimeZone(in); 047 } 048 } 049 ); 050 add(TimeZone.class, String.class, 051 new Transform<TimeZone,String>() { 052 @Override public String transform(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 Transform<String,Locale>() { 061 @Override 062 public Locale transform(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 Transform<String,Boolean>() { 071 @Override 072 public Boolean transform(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, Transform<?,?> t) { 089 Map<Class<?>,Transform<?,?>> 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> Transform<I,O> get(final Class<I> ic, final Class<O> oc) { 106 107 if (ic == null || oc == null) 108 return null; 109 110 Map<Class<?>,Transform<?,?>> 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 Transform t = m.get(ic); 118 if (t != null) 119 return t == NULL ? null : t; 120 121 for (Iterator<Class<?>> i = ClassUtils.getParentClasses(ic, false, true); i.hasNext(); ) { 122 Class pic = i.next(); 123 t = m.get(pic); 124 if (t != null) { 125 m.put(pic, t); 126 return t == NULL ? null : t; 127 } 128 } 129 130 if (ic == oc) { 131 t = new Transform<I,O>() { 132 @Override public O transform(Object outer, I in) { 133 return (O)in; 134 } 135 }; 136 } else if (ic == String.class) { 137 final Class<?> oc2 = hasPrimitiveWrapper(oc) ? getPrimitiveWrapper(oc) : oc; 138 if (oc2.isEnum()) { 139 t = new Transform<String,O>() { 140 @Override 141 public O transform(Object outer, String in) { 142 return (O)Enum.valueOf((Class<? extends Enum>)oc2, in); 143 } 144 }; 145 } else { 146 final Method fromStringMethod = findPublicFromStringMethod(oc2); 147 if (fromStringMethod != null) { 148 t = new Transform<String,O>() { 149 @Override 150 public O transform(Object outer, String in) { 151 try { 152 return (O)fromStringMethod.invoke(null, in); 153 } catch (Exception e) { 154 throw new RuntimeException(e); 155 } 156 } 157 }; 158 } 159 } 160 } 161 162 if (t == null) { 163 Method createMethod = findPublicStaticCreateMethod(oc, ic, "create"); 164 if (createMethod == null) 165 createMethod = findPublicStaticCreateMethod(oc, ic, "from" + ic.getSimpleName()); 166 if (createMethod != null) { 167 final Method cm = createMethod; 168 t = new Transform<I,O>() { 169 @Override 170 public O transform(Object context, I in) { 171 try { 172 return (O)cm.invoke(null, in); 173 } catch (Exception e) { 174 throw new RuntimeException(e); 175 } 176 } 177 }; 178 } else { 179 final Constructor<?> c = findPublicConstructor(oc, ic); 180 final boolean isMemberClass = oc.isMemberClass() && ! isStatic(oc); 181 if (c != null && ! isDeprecated(c)) { 182 t = new Transform<I,O>() { 183 @Override 184 public O transform(Object outer, I in) { 185 try { 186 if (isMemberClass) 187 return (O)c.newInstance(outer, in); 188 return (O)c.newInstance(in); 189 } catch (Exception e) { 190 throw new RuntimeException(e); 191 } 192 } 193 }; 194 } 195 196 } 197 } 198 199 if (t == null) { 200 for (Method m2 : getAllMethods(ic, false)) { 201 if (isAll(m2, PUBLIC, NOT_STATIC, HAS_NO_ARGS, NOT_DEPRECATED) && m2.getName().startsWith("to") && m2.getReturnType() == oc) { 202 final Method m3 = m2; 203 t = new Transform<I,O>() { 204 @Override 205 public O transform(Object outer, I in) { 206 try { 207 return (O)m3.invoke(in); 208 } catch (Exception e) { 209 throw new RuntimeException(e); 210 } 211 } 212 }; 213 break; 214 } 215 } 216 } 217 if (t == null) 218 t = NULL; 219 220 m.put(ic, t); 221 222 return t == NULL ? null : t; 223 } 224}