001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.swap; 018 019import static org.apache.juneau.commons.utils.CollectionUtils.*; 020import static org.apache.juneau.commons.utils.Utils.*; 021import static org.apache.juneau.internal.ClassUtils2.*; 022 023import java.lang.reflect.*; 024import java.util.*; 025 026import org.apache.juneau.*; 027import org.apache.juneau.annotation.*; 028import org.apache.juneau.commons.reflect.*; 029import org.apache.juneau.internal.*; 030import org.apache.juneau.parser.*; 031import org.apache.juneau.serializer.*; 032 033/** 034 * A dynamic object swap based on reflection of a Java class that converts Objects to Number serializable objects. 035 * 036 * <p> 037 * Looks for methods on the class that can be called to swap-in surrogate Number objects before serialization and swap-out 038 * surrogate Number objects after parsing. 039 * 040 * <h5 class='figure'>Valid surrogate objects</h5> 041 * <ul> 042 * <li class='jc'>Any subclass of {@link Number} 043 * <li class='jc'>Any number primitive 044 * </ul> 045 * 046 * <h5 class='figure'>Valid swap methods (S = Swapped type)</h5> 047 * <ul> 048 * <li class='jm'><c><jk>public</jk> S toNumber()</c> 049 * <li class='jm'><c><jk>public</jk> S toNumber(BeanSession)</c> 050 * <li class='jm'><c><jk>public</jk> S toInteger()</c> 051 * <li class='jm'><c><jk>public</jk> S toInteger(BeanSession)</c> 052 * <li class='jm'><c><jk>public</jk> S toInt()</c> 053 * <li class='jm'><c><jk>public</jk> S toInt(BeanSession)</c> 054 * <li class='jm'><c><jk>public</jk> S toLong()</c> 055 * <li class='jm'><c><jk>public</jk> S toLong(BeanSession)</c> 056 * <li class='jm'><c><jk>public</jk> S toFloat()</c> 057 * <li class='jm'><c><jk>public</jk> S toFloat(BeanSession)</c> 058 * <li class='jm'><c><jk>public</jk> S toDouble()</c> 059 * <li class='jm'><c><jk>public</jk> S toDouble(BeanSession)</c> 060 * <li class='jm'><c><jk>public</jk> S toShort()</c> 061 * <li class='jm'><c><jk>public</jk> S toShort(BeanSession)</c> 062 * <li class='jm'><c><jk>public</jk> S toByte()</c> 063 * <li class='jm'><c><jk>public</jk> S toByte(BeanSession)</c> 064 * </ul> 065 * 066 * <h5 class='figure'>Valid unswap methods (N = Normal type, S = Swapped type)</h5> 067 * <ul> 068 * <li class='jm'><c><jk>public static</jk> N fromInteger(S)</c> 069 * <li class='jm'><c><jk>public static</jk> N fromInteger(BeanSession, S)</c> 070 * <li class='jm'><c><jk>public static</jk> N fromInt(S)</c> 071 * <li class='jm'><c><jk>public static</jk> N fromInt(BeanSession, S)</c> 072 * <li class='jm'><c><jk>public static</jk> N fromLong(S)</c> 073 * <li class='jm'><c><jk>public static</jk> N fromLong(BeanSession, S)</c> 074 * <li class='jm'><c><jk>public static</jk> N fromFloat(S)</c> 075 * <li class='jm'><c><jk>public static</jk> N fromFloat(BeanSession, S)</c> 076 * <li class='jm'><c><jk>public static</jk> N fromDouble(S)</c> 077 * <li class='jm'><c><jk>public static</jk> N fromDouble(BeanSession, S)</c> 078 * <li class='jm'><c><jk>public static</jk> N fromShort(S)</c> 079 * <li class='jm'><c><jk>public static</jk> N fromShort(BeanSession, S)</c> 080 * <li class='jm'><c><jk>public static</jk> N fromByte(S)</c> 081 * <li class='jm'><c><jk>public static</jk> N fromByte(BeanSession, S)</c> 082 * <li class='jm'><c><jk>public static</jk> N create(S)</c> 083 * <li class='jm'><c><jk>public static</jk> N create(BeanSession, S)</c> 084 * <li class='jm'><c><jk>public static</jk> N valueOf(S)</c> 085 * <li class='jm'><c><jk>public static</jk> N valueOf(BeanSession, S)</c> 086 * <li class='jm'><c><jk>public</jk> N(S)</c> 087 * </ul> 088 * <p> 089 * Classes are ignored if any of the following are true: 090 * <ul> 091 * <li>Classes annotated with {@link BeanIgnore @BeanIgnore}. 092 * <li>Non-static member classes. 093 * </ul> 094 * 095 * <p> 096 * Members/constructors are ignored if any of the following are true: 097 * <ul> 098 * <li>Members/constructors annotated with {@link BeanIgnore @BeanIgnore}. 099 * <li>Deprecated members/constructors. 100 * </ul> 101 * 102 * <h5 class='section'>See Also:</h5><ul> 103 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SwapBasics">Swap Basics</a> 104 * </ul> 105 * 106 * @param <T> The normal class type. 107 */ 108public class AutoNumberSwap<T> extends ObjectSwap<T,Number> { 109 110 // @formatter:off 111 private static final Set<String> 112 SWAP_METHOD_NAMES = u(set("toNumber", "toInteger", "toInt", "toLong", "toFloat", "toDouble", "toShort", "toByte")), 113 UNSWAP_METHOD_NAMES = u(set("fromInteger", "fromInt", "fromLong", "fromFloat", "fromDouble", "fromShort", "fromByte", "create", "valueOf")); 114 // @formatter:on 115 116 /** 117 * Look for constructors and methods on this class and construct a dynamic swap if it's possible to do so. 118 * 119 * @param bc The bean context to use for looking up annotations. 120 * @param ci The class to try to constructor a dynamic swap on. 121 * @return An object swap instance, or <jk>null</jk> if one could not be created. 122 */ 123 @SuppressWarnings({ "rawtypes" }) 124 public static ObjectSwap<?,?> find(BeanContext bc, ClassInfo ci) { 125 126 if (shouldIgnore(bc, ci)) 127 return null; 128 129 // Find swap() method if present. 130 for (var m : ci.getAllMethods()) { 131 132 if (isSwapMethod(bc, m)) { 133 134 var rt = m.getReturnType(); 135 136 var mi = ci.getMethod(x -> isUnswapMethod(bc, x, ci, rt)).orElse(null); 137 if (nn(mi)) 138 return new AutoNumberSwap(bc, ci, m, mi, null); 139 140 var cs = ci.getDeclaredConstructor(x -> isUnswapConstructor(bc, x, rt)).orElse(null); 141 if (nn(cs)) 142 return new AutoNumberSwap(bc, ci, m, null, cs); 143 144 return new AutoNumberSwap(bc, ci, m, null, null); 145 } 146 } 147 148 return null; 149 } 150 151 private static boolean isSwapMethod(BeanContext bc, MethodInfo mi) { 152 var rt = mi.getReturnType(); 153 // @formatter:off 154 return 155 mi.isNotDeprecated() 156 && mi.isNotStatic() 157 && mi.isVisible(bc.getBeanMethodVisibility()) 158 && (rt.isChildOf(Number.class) || (rt.isPrimitive() && rt.isAny(int.class, short.class, long.class, float.class, double.class, byte.class))) 159 && mi.hasAnyName(SWAP_METHOD_NAMES) 160 && mi.hasParameterTypesLenient(BeanSession.class) 161 && ! mi.getMatchingMethods().stream().anyMatch(m2 -> bc.getAnnotationProvider().has(BeanIgnore.class, m2)); 162 // @formatter:on 163 } 164 165 private static boolean isUnswapConstructor(BeanContext bc, ConstructorInfo cs, ClassInfo rt) { 166 // @formatter:off 167 return 168 cs.isNotDeprecated() 169 && cs.isVisible(bc.getBeanConstructorVisibility()) 170 && cs.hasParameterTypeParents(rt) 171 && ! bc.getAnnotationProvider().has(BeanIgnore.class, cs); 172 // @formatter:on 173 } 174 175 private static boolean isUnswapMethod(BeanContext bc, MethodInfo mi, ClassInfo ci, ClassInfo rt) { 176 // @formatter:off 177 return 178 mi.isNotDeprecated() 179 && mi.isStatic() 180 && mi.isVisible(bc.getBeanMethodVisibility()) 181 && mi.hasAnyName(UNSWAP_METHOD_NAMES) 182 && mi.hasParameterTypesLenient(BeanSession.class, rt.inner()) 183 && mi.hasReturnTypeParent(ci) 184 && ! mi.getMatchingMethods().stream().anyMatch(m2 -> bc.getAnnotationProvider().has(BeanIgnore.class, m2)); 185 // @formatter:on 186 } 187 188 private static boolean shouldIgnore(BeanContext bc, ClassInfo ci) { 189 // @formatter:off 190 return 191 ci.isNonStaticMemberClass() 192 || ci.isPrimitive() 193 || ci.isChildOf(Number.class) 194 || bc.getAnnotationProvider().has(BeanIgnore.class, ci); 195 // @formatter:on 196 } 197 198 //------------------------------------------------------------------------------------------------------------------ 199 200 private final Method swapMethod, unswapMethod; 201 private final Constructor<?> unswapConstructor; 202 private final Class<?> unswapType; 203 204 @SuppressWarnings("null") 205 private AutoNumberSwap(BeanContext bc, ClassInfo ci, MethodInfo swapMethod, MethodInfo unswapMethod, ConstructorInfo unswapConstructor) { 206 super(ci.inner(), swapMethod.inner().getReturnType()); 207 this.swapMethod = bc.getBeanMethodVisibility().transform(swapMethod.inner()); 208 this.unswapMethod = unswapMethod == null ? null : bc.getBeanMethodVisibility().transform(unswapMethod.inner()); 209 this.unswapConstructor = unswapConstructor == null ? null : bc.getBeanConstructorVisibility().transform(unswapConstructor.inner()); 210 211 var unswapType = (Class<?>)null; 212 if (nn(unswapMethod)) { 213 for (var pi : unswapMethod.getParameters()) 214 if (! pi.getParameterType().is(BeanSession.class)) 215 unswapType = pi.getParameterType().getWrapperIfPrimitive().inner(); 216 } else if (nn(unswapConstructor)) { 217 for (var pi : unswapConstructor.getParameters()) 218 if (! pi.getParameterType().is(BeanSession.class)) 219 unswapType = pi.getParameterType().getWrapperIfPrimitive().inner(); 220 } 221 this.unswapType = unswapType; 222 } 223 224 @Override /* Overridden from ObjectSwap */ 225 public Number swap(BeanSession session, Object o) throws SerializeException { 226 try { 227 return (Number)swapMethod.invoke(o, getMatchingArgs(swapMethod.getParameterTypes(), session)); 228 } catch (Exception e) { 229 throw SerializeException.create(e); 230 } 231 } 232 233 @SuppressWarnings("unchecked") 234 @Override /* Overridden from ObjectSwap */ 235 public T unswap(BeanSession session, Number o, ClassMeta<?> hint) throws ParseException { 236 if (unswapType == null) 237 throw new ParseException("No unparse methodology found for object."); 238 try { 239 Object o2 = ConverterUtils.toType(o, unswapType); 240 if (nn(unswapMethod)) 241 return (T)unswapMethod.invoke(null, getMatchingArgs(unswapMethod.getParameterTypes(), session, o2)); 242 if (nn(unswapConstructor)) 243 return (T)unswapConstructor.newInstance(o2); 244 return super.unswap(session, o, hint); 245 } catch (Exception e) { 246 throw ParseException.create(e); 247 } 248 } 249}