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