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}