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