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.swap;
014
015import static org.apache.juneau.internal.ClassUtils.*;
016import static org.apache.juneau.internal.CollectionUtils.*;
017
018import java.lang.reflect.*;
019import java.util.*;
020
021import org.apache.juneau.*;
022import org.apache.juneau.annotation.*;
023import org.apache.juneau.internal.*;
024import org.apache.juneau.parser.*;
025import org.apache.juneau.reflect.*;
026import org.apache.juneau.serializer.*;
027
028/**
029 * A dynamic object swap based on reflection of a Java class that converts Objects to Number serializable objects.
030 *
031 * <p>
032 * Looks for methods on the class that can be called to swap-in surrogate Number objects before serialization and swap-out
033 * surrogate Number objects after parsing.
034 *
035 * <h5 class='figure'>Valid surrogate objects</h5>
036 * <ul>
037 *    <li class='jc'>Any subclass of {@link Number}
038 *    <li class='jc'>Any number primitive
039 * </ul>
040 *
041 * <h5 class='figure'>Valid swap methods (S = Swapped type)</h5>
042 * <ul>
043 *    <li class='jm'><c><jk>public</jk> S toNumber()</c>
044 *    <li class='jm'><c><jk>public</jk> S toNumber(BeanSession)</c>
045 *    <li class='jm'><c><jk>public</jk> S toInteger()</c>
046 *    <li class='jm'><c><jk>public</jk> S toInteger(BeanSession)</c>
047 *    <li class='jm'><c><jk>public</jk> S toInt()</c>
048 *    <li class='jm'><c><jk>public</jk> S toInt(BeanSession)</c>
049 *    <li class='jm'><c><jk>public</jk> S toLong()</c>
050 *    <li class='jm'><c><jk>public</jk> S toLong(BeanSession)</c>
051 *    <li class='jm'><c><jk>public</jk> S toFloat()</c>
052 *    <li class='jm'><c><jk>public</jk> S toFloat(BeanSession)</c>
053 *    <li class='jm'><c><jk>public</jk> S toDouble()</c>
054 *    <li class='jm'><c><jk>public</jk> S toDouble(BeanSession)</c>
055 *    <li class='jm'><c><jk>public</jk> S toShort()</c>
056 *    <li class='jm'><c><jk>public</jk> S toShort(BeanSession)</c>
057 *    <li class='jm'><c><jk>public</jk> S toByte()</c>
058 *    <li class='jm'><c><jk>public</jk> S toByte(BeanSession)</c>
059 * </ul>
060 *
061 * <h5 class='figure'>Valid unswap methods (N = Normal type, S = Swapped type)</h5>
062 * <ul>
063 *    <li class='jm'><c><jk>public static</jk> N fromInteger(S)</c>
064 *    <li class='jm'><c><jk>public static</jk> N fromInteger(BeanSession, S)</c>
065 *    <li class='jm'><c><jk>public static</jk> N fromInt(S)</c>
066 *    <li class='jm'><c><jk>public static</jk> N fromInt(BeanSession, S)</c>
067 *    <li class='jm'><c><jk>public static</jk> N fromLong(S)</c>
068 *    <li class='jm'><c><jk>public static</jk> N fromLong(BeanSession, S)</c>
069 *    <li class='jm'><c><jk>public static</jk> N fromFloat(S)</c>
070 *    <li class='jm'><c><jk>public static</jk> N fromFloat(BeanSession, S)</c>
071 *    <li class='jm'><c><jk>public static</jk> N fromDouble(S)</c>
072 *    <li class='jm'><c><jk>public static</jk> N fromDouble(BeanSession, S)</c>
073 *    <li class='jm'><c><jk>public static</jk> N fromShort(S)</c>
074 *    <li class='jm'><c><jk>public static</jk> N fromShort(BeanSession, S)</c>
075 *    <li class='jm'><c><jk>public static</jk> N fromByte(S)</c>
076 *    <li class='jm'><c><jk>public static</jk> N fromByte(BeanSession, S)</c>
077 *    <li class='jm'><c><jk>public static</jk> N create(S)</c>
078 *    <li class='jm'><c><jk>public static</jk> N create(BeanSession, S)</c>
079 *    <li class='jm'><c><jk>public static</jk> N valueOf(S)</c>
080 *    <li class='jm'><c><jk>public static</jk> N valueOf(BeanSession, S)</c>
081 *    <li class='jm'><c><jk>public</jk> N(S)</c>
082 * </ul>
083 * <p>
084 * Classes are ignored if any of the following are true:
085 * <ul>
086 *    <li>Classes annotated with {@link BeanIgnore @BeanIgnore}.
087 *    <li>Non-static member classes.
088 * </ul>
089 *
090 * <p>
091 * Members/constructors are ignored if any of the following are true:
092 * <ul>
093 *    <li>Members/constructors annotated with {@link BeanIgnore @BeanIgnore}.
094 *    <li>Deprecated members/constructors.
095 * </ul>
096 *
097 * <h5 class='section'>See Also:</h5><ul>
098 *    <li class='link'><a class="doclink" href="../../../../index.html#jm.Swaps">Swaps</a>
099 * </ul>
100 *
101 * @param <T> The normal class type.
102 */
103public class AutoNumberSwap<T> extends ObjectSwap<T,Number> {
104
105   private static final Set<String>
106      SWAP_METHOD_NAMES = uset("toNumber", "toInteger", "toInt", "toLong", "toFloat", "toDouble", "toShort", "toByte"),
107      UNSWAP_METHOD_NAMES = uset("fromInteger", "fromInt", "fromLong", "fromFloat", "fromDouble", "fromShort", "fromByte", "create", "valueOf");
108
109   /**
110    * Look for constructors and methods on this class and construct a dynamic swap if it's possible to do so.
111    *
112    * @param bc The bean context to use for looking up annotations.
113    * @param ci The class to try to constructor a dynamic swap on.
114    * @return An object swap instance, or <jk>null</jk> if one could not be created.
115    */
116   @SuppressWarnings({ "rawtypes" })
117   public static ObjectSwap<?,?> find(BeanContext bc, ClassInfo ci) {
118
119      if (shouldIgnore(bc, ci))
120         return null;
121
122      // Find swap() method if present.
123      for (MethodInfo m : ci.getMethods()) {
124
125         if (isSwapMethod(bc, m)) {
126
127            ClassInfo rt = m.getReturnType();
128
129            MethodInfo mi = ci.getMethod(x -> isUnswapMethod(bc, x, ci, rt));
130            if (mi != null)
131               return new AutoNumberSwap(bc, ci, m, mi, null);
132
133            ConstructorInfo cs = ci.getDeclaredConstructor(x -> isUnswapConstructor(bc, x, rt));
134            if (cs != null)
135               return new AutoNumberSwap(bc, ci, m, null, cs);
136
137            return new AutoNumberSwap(bc, ci, m, null, null);
138         }
139      }
140
141      return null;
142   }
143
144   private static boolean shouldIgnore(BeanContext bc, ClassInfo ci) {
145      return
146         ci.hasAnnotation(bc, BeanIgnore.class)
147         || ci.isNonStaticMemberClass()
148         || ci.isPrimitive()
149         || ci.isChildOf(Number.class);
150   }
151
152   private static boolean isSwapMethod(BeanContext bc, MethodInfo mi) {
153      ClassInfo rt = mi.getReturnType();
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.hasName(SWAP_METHOD_NAMES)
160         && mi.hasFuzzyParamTypes(BeanSession.class)
161         && mi.hasNoAnnotation(bc, BeanIgnore.class);
162   }
163
164   private static boolean isUnswapMethod(BeanContext bc, MethodInfo mi, ClassInfo ci, ClassInfo rt) {
165      return
166         mi.isNotDeprecated()
167         && mi.isStatic()
168         && mi.isVisible(bc.getBeanMethodVisibility())
169         && mi.hasName(UNSWAP_METHOD_NAMES)
170         && mi.hasFuzzyParamTypes(BeanSession.class, rt.inner())
171         && mi.hasReturnTypeParent(ci)
172         && mi.hasNoAnnotation(bc, BeanIgnore.class);
173   }
174
175   private static boolean isUnswapConstructor(BeanContext bc, ConstructorInfo cs, ClassInfo rt) {
176      return
177         cs.isNotDeprecated()
178         && cs.isVisible(bc.getBeanConstructorVisibility())
179         && cs.hasMatchingParamTypes(rt)
180         && cs.hasNoAnnotation(bc, BeanIgnore.class);
181   }
182
183   //------------------------------------------------------------------------------------------------------------------
184
185   private final Method swapMethod, unswapMethod;
186   private final Constructor<?> unswapConstructor;
187   private final Class<?> unswapType;
188
189   private AutoNumberSwap(BeanContext bc, ClassInfo ci, MethodInfo swapMethod, MethodInfo unswapMethod, ConstructorInfo unswapConstructor) {
190      super(ci.inner(), swapMethod.inner().getReturnType());
191      this.swapMethod = bc.getBeanMethodVisibility().transform(swapMethod.inner());
192      this.unswapMethod = unswapMethod == null ? null : bc.getBeanMethodVisibility().transform(unswapMethod.inner());
193      this.unswapConstructor = unswapConstructor == null ? null : bc.getBeanConstructorVisibility().transform(unswapConstructor.inner());
194
195      Class<?> unswapType = null;
196      if (unswapMethod != null) {
197         for (ParamInfo pi : unswapMethod.getParams())
198            if (! pi.getParameterType().is(BeanSession.class))
199               unswapType = pi.getParameterType().getWrapperIfPrimitive();
200      } else if (unswapConstructor != null) {
201         for (ParamInfo pi : unswapConstructor.getParams())
202            if (! pi.getParameterType().is(BeanSession.class))
203               unswapType = pi.getParameterType().getWrapperIfPrimitive();
204      }
205      this.unswapType = unswapType;
206   }
207
208   @Override /* ObjectSwap */
209   public Number swap(BeanSession session, Object o) throws SerializeException {
210      try {
211         return (Number)swapMethod.invoke(o, getMatchingArgs(swapMethod.getParameterTypes(), session));
212      } catch (Exception e) {
213         throw SerializeException.create(e);
214      }
215   }
216
217   @SuppressWarnings("unchecked")
218   @Override /* ObjectSwap */
219   public T unswap(BeanSession session, Number o, ClassMeta<?> hint) throws ParseException {
220      if (unswapType == null)
221         throw new ParseException("No unparse methodology found for object.");
222      try {
223         Object o2 = ConverterUtils.toType(o, unswapType);
224         if (unswapMethod != null)
225            return (T)unswapMethod.invoke(null, getMatchingArgs(unswapMethod.getParameterTypes(), session, o2));
226         if (unswapConstructor != null)
227            return (T)unswapConstructor.newInstance(o2);
228         return super.unswap(session, o, hint);
229      } catch (Exception e) {
230         throw ParseException.create(e);
231      }
232   }
233}