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}