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.parser.*;
024import org.apache.juneau.reflect.*;
025import org.apache.juneau.serializer.*;
026
027/**
028 * A dynamic object swap based on reflection of a Java class that converts Objects to serializable objects.
029 *
030 * <p>
031 * Looks for methods on the class that can be called to swap-in surrogate objects before serialization and swap-out
032 * surrogate objects after parsing.
033 *
034 * <h5 class='figure'>Valid surrogate objects</h5>
035 * <ul>
036 *    <li class='jc'>{@link String}
037 *    <li class='jc'>{@link Number}
038 *    <li class='jc'>{@link Boolean}
039 *    <li class='jc'>{@link Map}
040 *    <li class='jc'>{@link Collection}
041 * </ul>
042 *
043 * <h5 class='figure'>Valid swap methods (S = Swapped type)</h5>
044 * <ul>
045 *    <li class='jm'><c><jk>public</jk> S swap()</c>
046 *    <li class='jm'><c><jk>public</jk> S swap(BeanSession)</c>
047 *    <li class='jm'><c><jk>public</jk> S toObject()</c>
048 *    <li class='jm'><c><jk>public</jk> S toObject(BeanSession)</c>
049 * </ul>
050 *
051 * <h5 class='figure'>Valid unswap methods (N = Normal type, S = Swapped type)</h5>
052 * <ul>
053 *    <li class='jm'><c><jk>public static</jk> N unswap(S)</c>
054 *    <li class='jm'><c><jk>public static</jk> N unswap(BeanSession, S)</c>
055 *    <li class='jm'><c><jk>public static</jk> N fromObject(S)</c>
056 *    <li class='jm'><c><jk>public static</jk> N fromObject(BeanSession, S)</c>
057 *    <li class='jm'><c><jk>public static</jk> N create(S)</c>
058 *    <li class='jm'><c><jk>public static</jk> N create(BeanSession, S)</c>
059 *    <li class='jm'><c><jk>public</jk> N(S)</c>
060 * </ul>
061 *
062 * <p>
063 * Classes are ignored if any of the following are true:
064 * <ul>
065 *    <li>Classes annotated with {@link BeanIgnore @BeanIgnore}.
066 *    <li>Non-static member classes.
067 * </ul>
068 *
069 * <p>
070 * Members/constructors are ignored if any of the following are true:
071 * <ul>
072 *    <li>Members/constructors annotated with {@link BeanIgnore @BeanIgnore}.
073 *    <li>Deprecated members/constructors.
074 * </ul>
075 *
076 * <h5 class='section'>See Also:</h5><ul>
077 *    <li class='link'><a class="doclink" href="../../../../index.html#jm.Swaps">Swaps</a>
078 * </ul>
079 *
080 * @param <T> The normal class type.
081 */
082public class AutoObjectSwap<T> extends ObjectSwap<T,Object> {
083
084   private static final Set<String>
085      SWAP_METHOD_NAMES = uset("swap", "toObject"),
086      UNSWAP_METHOD_NAMES = uset("unswap", "create", "fromObject", "of");
087
088   /**
089    * Inspects the specified class and returns a swap of this type if possible.
090    *
091    * @param bc The bean context to use for looking up annotations.
092    * @param ci The class to return a swap on.
093    * @return An object swap instance, or <jk>null</jk> if one could not be created.
094    */
095   @SuppressWarnings({ "rawtypes" })
096   public static ObjectSwap<?,?> find(BeanContext bc, ClassInfo ci) {
097
098      if (shouldIgnore(bc, ci))
099         return null;
100
101      // Find swap() method if present.
102      for (MethodInfo m : ci.getMethods()) {
103         if (isSwapMethod(bc, m)) {
104
105            ClassInfo rt = m.getReturnType();
106
107            MethodInfo mi = ci.getMethod(x -> isUnswapMethod(bc, x, ci, rt));
108            if (mi != null)
109               return new AutoObjectSwap(bc, ci, m, mi, null);
110
111            ConstructorInfo cs = ci.getDeclaredConstructor(x -> isUnswapConstructor(bc, x, rt));
112            if (cs != null)
113               return new AutoObjectSwap(bc, ci, m, null, cs);
114
115            return new AutoObjectSwap(bc, ci, m, null, null);
116         }
117      }
118
119      return null;
120   }
121
122   private static boolean shouldIgnore(BeanContext bc, ClassInfo ci) {
123      return
124         ci.hasAnnotation(bc, BeanIgnore.class)
125         || ci.isNonStaticMemberClass();
126   }
127
128   private static boolean isSwapMethod(BeanContext bc, MethodInfo mi) {
129      return
130         mi.isNotDeprecated()
131         && mi.isNotStatic()
132         && mi.isVisible(bc.getBeanMethodVisibility())
133         && mi.hasName(SWAP_METHOD_NAMES)
134         && mi.hasFuzzyParamTypes(BeanSession.class)
135         && mi.hasNoAnnotation(bc, BeanIgnore.class);
136   }
137
138   private static boolean isUnswapMethod(BeanContext bc, MethodInfo mi, ClassInfo ci, ClassInfo rt) {
139      return
140         mi.isNotDeprecated()
141         && mi.isStatic()
142         && mi.isVisible(bc.getBeanMethodVisibility())
143         && mi.hasName(UNSWAP_METHOD_NAMES)
144         && mi.hasFuzzyParamTypes(BeanSession.class, rt.inner())
145         && mi.hasReturnTypeParent(ci)
146         && mi.hasNoAnnotation(bc, BeanIgnore.class);
147   }
148
149   private static boolean isUnswapConstructor(BeanContext bc, ConstructorInfo cs, ClassInfo rt) {
150      return
151         cs.isNotDeprecated()
152         && cs.isVisible(bc.getBeanConstructorVisibility())
153         && cs.hasMatchingParamTypes(rt)
154         && cs.hasNoAnnotation(bc, BeanIgnore.class);
155   }
156
157   //------------------------------------------------------------------------------------------------------------------
158
159   private final Method swapMethod, unswapMethod;
160   private final Constructor<?> unswapConstructor;
161
162   private AutoObjectSwap(BeanContext bc, ClassInfo ci, MethodInfo swapMethod, MethodInfo unswapMethod, ConstructorInfo unswapConstructor) {
163      super(ci.inner(), swapMethod.inner().getReturnType());
164      this.swapMethod = bc.getBeanMethodVisibility().transform(swapMethod.inner());
165      this.unswapMethod = unswapMethod == null ? null : bc.getBeanMethodVisibility().transform(unswapMethod.inner());
166      this.unswapConstructor = unswapConstructor == null ? null : bc.getBeanConstructorVisibility().transform(unswapConstructor.inner());
167   }
168
169   @Override /* ObjectSwap */
170   public Object swap(BeanSession session, Object o) throws SerializeException {
171      try {
172         return swapMethod.invoke(o, getMatchingArgs(swapMethod.getParameterTypes(), session));
173      } catch (Exception e) {
174         throw SerializeException.create(e);
175      }
176   }
177
178   @SuppressWarnings("unchecked")
179   @Override /* ObjectSwap */
180   public T unswap(BeanSession session, Object f, ClassMeta<?> hint) throws ParseException {
181      try {
182         if (unswapMethod != null)
183            return (T)unswapMethod.invoke(null, getMatchingArgs(unswapMethod.getParameterTypes(), session, f));
184         if (unswapConstructor != null)
185            return (T)unswapConstructor.newInstance(f);
186         return super.unswap(session, f, hint);
187      } catch (Exception e) {
188         throw ParseException.create(e);
189      }
190   }
191}