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.marshall.swap.spi; 018 019import static org.apache.juneau.commons.utils.CollectionUtils.*; 020import static org.apache.juneau.commons.utils.Shorts.*; 021import static org.apache.juneau.marshall.internal.ClassUtils2.*; 022 023import java.lang.reflect.*; 024import java.util.*; 025 026import org.apache.juneau.commons.bean.*; 027import org.apache.juneau.commons.conversion.*; 028import org.apache.juneau.commons.reflect.*; 029import org.apache.juneau.marshall.*; 030import org.apache.juneau.marshall.parser.*; 031import org.apache.juneau.marshall.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(MarshallingSession)</c> 050 * <li class='jm'><c><jk>public</jk> S toInteger()</c> 051 * <li class='jm'><c><jk>public</jk> S toInteger(MarshallingSession)</c> 052 * <li class='jm'><c><jk>public</jk> S toInt()</c> 053 * <li class='jm'><c><jk>public</jk> S toInt(MarshallingSession)</c> 054 * <li class='jm'><c><jk>public</jk> S toLong()</c> 055 * <li class='jm'><c><jk>public</jk> S toLong(MarshallingSession)</c> 056 * <li class='jm'><c><jk>public</jk> S toFloat()</c> 057 * <li class='jm'><c><jk>public</jk> S toFloat(MarshallingSession)</c> 058 * <li class='jm'><c><jk>public</jk> S toDouble()</c> 059 * <li class='jm'><c><jk>public</jk> S toDouble(MarshallingSession)</c> 060 * <li class='jm'><c><jk>public</jk> S toShort()</c> 061 * <li class='jm'><c><jk>public</jk> S toShort(MarshallingSession)</c> 062 * <li class='jm'><c><jk>public</jk> S toByte()</c> 063 * <li class='jm'><c><jk>public</jk> S toByte(MarshallingSession)</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(MarshallingSession, 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(MarshallingSession, 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(MarshallingSession, 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(MarshallingSession, 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(MarshallingSession, 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(MarshallingSession, 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(MarshallingSession, 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(MarshallingSession, 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(MarshallingSession, 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/Swaps">Swap</a> 104 * </ul> 105 * 106 * @param <T> The normal class type. 107 */ 108@SuppressWarnings({ 109 "java:S3776" // The long &&/|| condition chains in isSwapMethod()/isUnswapMethod() and the nested loops in the constructor. 110}) 111public class AutoNumberSwap<T> extends ObjectSwap<T,Number> { 112 113 private static final Set<String> SWAP_METHOD_NAMES = u(set("toNumber", "toInteger", "toInt", "toLong", "toFloat", "toDouble", "toShort", "toByte")); 114 private static final Set<String> UNSWAP_METHOD_NAMES = u(set("fromInteger", "fromInt", "fromLong", "fromFloat", "fromDouble", "fromShort", "fromByte", "create", "valueOf")); 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. Must not be <jk>null</jk>. 120 * @param ci The class to try to constructor a dynamic swap on. Must not be <jk>null</jk>. 121 * @return An object swap instance, or <jk>null</jk> if one could not be created. 122 */ 123 @SuppressWarnings({ 124 "java:S1452", // Wildcard required - ObjectSwap<?,?> for dynamically discovered swap types 125 "rawtypes" // Raw types necessary for generic type handling 126 }) 127 public static ObjectSwap<?,?> find(MarshallingContext bc, ClassInfo ci) { 128 129 if (shouldIgnore(bc, ci)) 130 return null; 131 132 // Find swap() method if present. 133 for (var m : ci.getAllMethods()) { 134 135 if (isSwapMethod(bc, m)) { 136 137 var rt = m.getReturnType(); 138 139 var mi = ci.getMethod(x -> isUnswapMethod(bc, x, ci, rt)).orElse(null); 140 if (nn(mi)) 141 return new AutoNumberSwap(bc, ci, m, mi, null); 142 143 var cs = ci.getDeclaredConstructor(x -> isUnswapConstructor(bc, x, rt)).orElse(null); 144 if (nn(cs)) 145 return new AutoNumberSwap(bc, ci, m, null, cs); 146 147 return new AutoNumberSwap(bc, ci, m, null, null); 148 } 149 } 150 151 return null; 152 } 153 154 private static boolean isSwapMethod(MarshallingContext bc, MethodInfo mi) { 155 var rt = mi.getReturnType(); 156 // @formatter:off 157 return 158 mi.isNotDeprecated() 159 && mi.isNotStatic() 160 && mi.isVisible(bc.getBeanMethodVisibility()) 161 && (rt.isAssignableTo(Number.class) || (rt.isPrimitive() && rt.isAny(int.class, short.class, long.class, float.class, double.class, byte.class))) 162 && mi.hasAnyName(SWAP_METHOD_NAMES) 163 && mi.hasParameterTypesLenient(MarshallingSession.class) 164 && mi.getMatchingMethods().stream().noneMatch(m2 -> bc.getAnnotationProvider().has(BeanIgnore.class, m2)); 165 // @formatter:on 166 } 167 168 private static boolean isUnswapConstructor(MarshallingContext bc, ConstructorInfo cs, ClassInfo rt) { 169 // @formatter:off 170 return 171 cs.isNotDeprecated() 172 && cs.isVisible(bc.getBeanConstructorVisibility()) 173 && cs.hasParameterTypeParents(rt) 174 && ! bc.getAnnotationProvider().has(BeanIgnore.class, cs); 175 // @formatter:on 176 } 177 178 private static boolean isUnswapMethod(MarshallingContext bc, MethodInfo mi, ClassInfo ci, ClassInfo rt) { 179 // @formatter:off 180 return 181 mi.isNotDeprecated() 182 && mi.isStatic() 183 && mi.isVisible(bc.getBeanMethodVisibility()) 184 && mi.hasAnyName(UNSWAP_METHOD_NAMES) 185 && mi.hasParameterTypesLenient(MarshallingSession.class, rt.inner()) 186 && mi.hasReturnTypeParent(ci) 187 && mi.getMatchingMethods().stream().noneMatch(m2 -> bc.getAnnotationProvider().has(BeanIgnore.class, m2)); 188 // @formatter:on 189 } 190 191 private static boolean shouldIgnore(MarshallingContext bc, ClassInfo ci) { 192 // @formatter:off 193 return 194 ci.isNonStaticMemberClass() 195 || ci.isPrimitive() 196 || ci.isAssignableTo(Number.class) 197 || bc.getAnnotationProvider().has(BeanIgnore.class, ci); 198 // @formatter:on 199 } 200 201 //------------------------------------------------------------------------------------------------------------------ 202 203 private final Method swapMethod; 204 private final Method unswapMethod; 205 private final Constructor<?> unswapConstructor; 206 private final Class<?> unswapType; 207 208 @SuppressWarnings({ 209 "null" // Method info variables are checked for null before use in method body 210 }) 211 private AutoNumberSwap(MarshallingContext bc, ClassInfo ci, MethodInfo swapMethod, MethodInfo unswapMethod, ConstructorInfo unswapConstructor) { 212 super(ci.inner(), swapMethod.inner().getReturnType()); 213 this.swapMethod = bc.getBeanMethodVisibility().transform(swapMethod.inner()); 214 this.unswapMethod = unswapMethod == null ? null : bc.getBeanMethodVisibility().transform(unswapMethod.inner()); 215 this.unswapConstructor = unswapConstructor == null ? null : bc.getBeanConstructorVisibility().transform(unswapConstructor.inner()); 216 217 Class<?> unswapType2 = null; 218 if (nn(unswapMethod)) { 219 for (var pi : unswapMethod.getParameters()) 220 if (! pi.getParameterType().is(MarshallingSession.class)) { 221 var wrap = pi.getParameterType().getWrapperIfPrimitive(); 222 if (nn(wrap)) 223 unswapType2 = wrap.inner(); 224 } 225 } else if (nn(unswapConstructor)) { 226 for (var pi : unswapConstructor.getParameters()) 227 if (! pi.getParameterType().is(MarshallingSession.class)) { 228 var wrap = pi.getParameterType().getWrapperIfPrimitive(); 229 if (nn(wrap)) 230 unswapType2 = wrap.inner(); 231 } 232 } 233 this.unswapType = unswapType2; 234 } 235 236 @Override /* Overridden from ObjectSwap */ 237 public Number swap(MarshallingSession session, Object o) throws SerializeException { 238 try { 239 return (Number)swapMethod.invoke(o, getMatchingArgs(swapMethod.getParameterTypes(), session)); 240 } catch (Exception e) { 241 throw SerializeException.create(e); 242 } 243 } 244 245 @SuppressWarnings({ 246 "unchecked" // Type erasure requires cast to T 247 }) 248 @Override /* Overridden from ObjectSwap */ 249 public T unswap(MarshallingSession session, Number o, ClassMeta<?> hint) throws ParseException { 250 if (unswapType == null) 251 throw new ParseException("No unparse methodology found for object."); 252 try { 253 Object o2 = BasicConverter.INSTANCE.to(o, unswapType); 254 if (nn(unswapMethod)) 255 return (T)unswapMethod.invoke(null, getMatchingArgs(unswapMethod.getParameterTypes(), session, o2)); 256 if (nn(unswapConstructor)) 257 return (T)unswapConstructor.newInstance(o2); 258 return super.unswap(session, o, hint); 259 } catch (Exception e) { 260 throw ParseException.create(e); 261 } 262 } 263}