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