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