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}