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.internal.ClassUtils.*; 020 021import java.util.*; 022 023import org.apache.juneau.*; 024import org.apache.juneau.annotation.*; 025import org.apache.juneau.parser.*; 026import org.apache.juneau.reflect.*; 027import org.apache.juneau.serializer.*; 028 029/** 030 * Used to swap out non-serializable objects with serializable replacements during serialization, and vis-versa during 031 * parsing. 032 * 033 * <h5 class='topic'>Description</h5> 034 * 035 * <p> 036 * <c>ObjectSwaps</c> are used to extend the functionality of the serializers and parsers to be able to handle 037 * objects that aren't automatically handled by the serializers or parsers. 038 * <br>For example, JSON does not have a standard representation for rendering dates. 039 * By defining a special {@code Date} swap and associating it with a serializer and parser, you can convert a 040 * {@code Date} object to a {@code String} during serialization, and convert that {@code String} object back into a 041 * {@code Date} object during parsing. 042 * 043 * <p> 044 * Swaps MUST declare a public no-arg constructor so that the bean context can instantiate them. 045 * 046 * <p> 047 * <c>ObjectSwaps</c> are associated with serializers and parsers through the following: 048 * <ul class='javatree'> 049 * <li class='ja'>{@link Swap @Swap} 050 * <li class='jm'>{@link org.apache.juneau.BeanContext.Builder#swaps(Class...)} 051 * </ul> 052 * 053 * <p> 054 * <c>ObjectSwaps</c> have two parameters: 055 * <ol> 056 * <li>{@code <T>} - The normal representation of an object. 057 * <li>{@code <S>} - The swapped representation of an object. 058 * </ol> 059 * <br>{@link Serializer Serializers} use swaps to convert objects of type T into objects of type S, and on calls to 060 * {@link BeanMap#get(Object)}. 061 * <br>{@link Parser Parsers} use swaps to convert objects of type S into objects of type T, and on calls to 062 * {@link BeanMap#put(String,Object)}. 063 * 064 * <h5 class='topic'>Swap Class Type {@code <S>}</h5> 065 * 066 * <p> 067 * For normal serialization, the swapped object representation of an object must be an object type that the serializers can natively convert to 068 * JSON (or language-specific equivalent). 069 * <br>The list of valid transformed types are as follows... 070 * <ul> 071 * <li> 072 * {@link String} 073 * <li> 074 * {@link Number} 075 * <li> 076 * {@link Boolean} 077 * <li> 078 * {@link Collection} containing anything on this list. 079 * <li> 080 * {@link Map} containing anything on this list. 081 * <li> 082 * A java bean with properties of anything on this list. 083 * <li> 084 * An array of anything on this list. 085 * </ul> 086 * 087 * <p> 088 * For OpenAPI serialization, the valid swapped types also include <code><jk>byte</jk>[]</code> and <c>Calendar</c>. 089 * 090 * <h5 class='topic'>Normal Class Type {@code <T>}</h5> 091 * 092 * <p> 093 * The normal object representation of an object. 094 * 095 * <h5 class='section'>See Also:</h5><ul> 096 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SwapBasics">Swap Basics</a> 097 098 * </ul> 099 * 100 * @param <T> The normal form of the class. 101 * @param <S> The swapped form of the class. 102 */ 103@SuppressWarnings({"unchecked","rawtypes"}) 104public abstract class ObjectSwap<T,S> { 105 106 /** 107 * Represents a non-existent object swap. 108 */ 109 public static final ObjectSwap NULL = new ObjectSwap((Class)null, (Class)null) {}; 110 111 private final Class<T> normalClass; 112 private final Class<?> swapClass; 113 private final ClassInfo normalClassInfo, swapClassInfo; 114 private ClassMeta<?> swapClassMeta; 115 116 // Unfortunately these cannot be made final because we want to allow for ObjectSwaps with no-arg constructors 117 // which simplifies declarations. 118 private MediaType[] forMediaTypes; 119 private String template; 120 121 /** 122 * Constructor. 123 */ 124 protected ObjectSwap() { 125 ClassInfo ci = ClassInfo.of(this.getClass()); 126 normalClass = (Class<T>)ci.getParameterType(0, ObjectSwap.class); 127 swapClass = ci.getParameterType(1, ObjectSwap.class); 128 normalClassInfo = ClassInfo.of(normalClass); 129 swapClassInfo = ClassInfo.of(swapClass); 130 forMediaTypes = forMediaTypes(); 131 template = withTemplate(); 132 } 133 134 /** 135 * Constructor for when the normal and transformed classes are already known. 136 * 137 * @param normalClass The normal class (cannot be serialized). 138 * @param swapClass The transformed class (serializable). 139 */ 140 protected ObjectSwap(Class<T> normalClass, Class<?> swapClass) { 141 this.normalClass = normalClass; 142 this.swapClass = swapClass; 143 normalClassInfo = ClassInfo.of(normalClass); 144 swapClassInfo = ClassInfo.of(swapClass); 145 this.forMediaTypes = forMediaTypes(); 146 this.template = withTemplate(); 147 } 148 149 /** 150 * Returns the media types that this swap is applicable to. 151 * 152 * <p> 153 * This method can be overridden to programmatically specify what media types it applies to. 154 * 155 * <p> 156 * This method is the programmatic equivalent to the {@link Swap#mediaTypes() @Swap(mediaTypes)} annotation. 157 * 158 * <h5 class='section'>See Also:</h5><ul> 159 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/PerMediaTypeSwaps">Per-media-type Swaps</a> 160 * </ul> 161 * @return The media types that this swap is applicable to, or <jk>null</jk> if it's applicable for all media types. 162 */ 163 public MediaType[] forMediaTypes() { 164 return null; 165 } 166 167 /** 168 * Returns additional context information for this swap. 169 * 170 * <p> 171 * Typically this is going to be used to specify a template name, such as a FreeMarker template file name. 172 * 173 * <p> 174 * This method can be overridden to programmatically specify a template value. 175 * 176 * <p> 177 * This method is the programmatic equivalent to the {@link Swap#template() @Swap(template)} annotation. 178 * 179 * <h5 class='section'>See Also:</h5><ul> 180 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/TemplatedSwaps">Templated Swaps</a> 181 * </ul> 182 * 183 * @return Additional context information, or <jk>null</jk> if not specified. 184 */ 185 public String withTemplate() { 186 return null; 187 } 188 189 /** 190 * Sets the media types that this swap is associated with. 191 * 192 * <h5 class='section'>See Also:</h5><ul> 193 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/PerMediaTypeSwaps">Per-media-type Swaps</a> 194 * </ul> 195 * 196 * @param mediaTypes The media types that this swap is associated with. 197 * @return This object. 198 */ 199 public ObjectSwap<T,?> forMediaTypes(MediaType[] mediaTypes) { 200 this.forMediaTypes = mediaTypes; 201 return this; 202 } 203 204 /** 205 * Sets the template string on this swap. 206 * 207 * <h5 class='section'>See Also:</h5><ul> 208 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/TemplatedSwaps">Templated Swaps</a> 209 * </ul> 210 * 211 * @param template The template string on this swap. 212 * @return This object. 213 */ 214 public ObjectSwap<T,?> withTemplate(String template) { 215 this.template = template; 216 return this; 217 } 218 219 /** 220 * Returns a number indicating how well this swap matches the specified session. 221 * 222 * <p> 223 * Uses the {@link MediaType#match(MediaType, boolean)} method algorithm to produce a number whereby a 224 * larger value indicates a "better match". 225 * The idea being that if multiple swaps are associated with a given object, we want to pick the best one. 226 * 227 * <p> 228 * For example, if the session media type is <js>"text/json"</js>, then the match values are shown below: 229 * 230 * <ul> 231 * <li><js>"text/json"</js> = <c>100,000</c> 232 * <li><js>"*/json"</js> = <c>5,100</c> 233 * <li><js>"*/*"</js> = <c>5,000</c> 234 * <li>No media types specified on swap = <c>1</c> 235 * <li><js>"text/xml"</js> = <c>0</c> 236 * </ul> 237 * 238 * @param session The bean session. 239 * @return Zero if swap doesn't match the session, or a positive number if it does. 240 */ 241 public int match(BeanSession session) { 242 if (forMediaTypes == null) 243 return 1; 244 int i = 0; 245 MediaType mt = session.getMediaType(); 246 if (mt == null) 247 return 0; 248 if (forMediaTypes != null) 249 for (MediaType mt2 : forMediaTypes) 250 i = Math.max(i, mt2.match(mt, false)); 251 return i; 252 } 253 254 /** 255 * If this transform is to be used to serialize non-serializable objects, it must implement this method. 256 * 257 * <p> 258 * The object must be converted into one of the following serializable types: 259 * <ul class='spaced-list'> 260 * <li> 261 * {@link String} 262 * <li> 263 * {@link Number} 264 * <li> 265 * {@link Boolean} 266 * <li> 267 * {@link Collection} containing anything on this list. 268 * <li> 269 * {@link Map} containing anything on this list. 270 * <li> 271 * A java bean with properties of anything on this list. 272 * <li> 273 * An array of anything on this list. 274 * </ul> 275 * 276 * @param session 277 * The bean session to use to get the class meta. 278 * This is always going to be the same bean context that created this swap. 279 * @param o The object to be transformed. 280 * @return The transformed object. 281 * @throws Exception If a problem occurred trying to convert the output. 282 */ 283 public S swap(BeanSession session, T o) throws Exception { 284 return swap(session, o, template); 285 } 286 287 /** 288 * Same as {@link #swap(BeanSession, Object)}, but can be used if your swap has a template associated with it. 289 * 290 * @param session 291 * The bean session to use to get the class meta. 292 * This is always going to be the same bean context that created this swap. 293 * @param o The object to be transformed. 294 * @param template 295 * The template string associated with this swap. 296 * @return The transformed object. 297 * @throws Exception If a problem occurred trying to convert the output. 298 */ 299 public S swap(BeanSession session, T o, String template) throws Exception { 300 throw new SerializeException("Swap method not implemented on ObjectSwap ''{0}''", className(this)); 301 } 302 303 /** 304 * If this transform is to be used to reconstitute objects that aren't true Java beans, it must implement this method. 305 * 306 * @param session 307 * The bean session to use to get the class meta. 308 * This is always going to be the same bean context that created this swap. 309 * @param f The transformed object. 310 * @param hint 311 * If possible, the parser will try to tell you the object type being created. 312 * For example, on a serialized date, this may tell you that the object being created must be of type 313 * {@code GregorianCalendar}. 314 * <br>This may be <jk>null</jk> if the parser cannot make this determination. 315 * @return The narrowed object. 316 * @throws Exception If this method is not implemented. 317 */ 318 public T unswap(BeanSession session, S f, ClassMeta<?> hint) throws Exception { 319 return unswap(session, f, hint, template); 320 } 321 322 /** 323 * Same as {@link #unswap(BeanSession, Object, ClassMeta)}, but can be used if your swap has a template associated with it. 324 * 325 * @param session 326 * The bean session to use to get the class meta. 327 * This is always going to be the same bean context that created this swap. 328 * @param f The transformed object. 329 * @param hint 330 * If possible, the parser will try to tell you the object type being created. 331 * For example, on a serialized date, this may tell you that the object being created must be of type 332 * {@code GregorianCalendar}. 333 * <br>This may be <jk>null</jk> if the parser cannot make this determination. 334 * @param template 335 * The template string associated with this swap. 336 * @return The transformed object. 337 * @throws Exception If a problem occurred trying to convert the output. 338 */ 339 public T unswap(BeanSession session, S f, ClassMeta<?> hint, String template) throws Exception { 340 throw new ParseException("Unswap method not implemented on ObjectSwap ''{0}''", className(this)); 341 } 342 343 /** 344 * Returns the T class, the normalized form of the class. 345 * 346 * @return The normal form of this class. 347 */ 348 public ClassInfo getNormalClass() { 349 return normalClassInfo; 350 } 351 352 /** 353 * Returns the G class, the generalized form of the class. 354 * 355 * <p> 356 * Subclasses must override this method if the generalized class is {@code Object}, meaning it can produce multiple 357 * generalized forms. 358 * 359 * @return The transformed form of this class. 360 */ 361 public ClassInfo getSwapClass() { 362 return swapClassInfo; 363 } 364 365 /** 366 * Returns the {@link ClassMeta} of the transformed class type. 367 * 368 * <p> 369 * This value is cached for quick lookup. 370 * 371 * @param session 372 * The bean context to use to get the class meta. 373 * This is always going to be the same bean context that created this swap. 374 * @return The {@link ClassMeta} of the transformed class type. 375 */ 376 public ClassMeta<?> getSwapClassMeta(BeanSession session) { 377 if (swapClassMeta == null) 378 swapClassMeta = session.getClassMeta(swapClass); 379 return swapClassMeta; 380 } 381 382 /** 383 * Checks if the specified object is an instance of the normal class defined on this swap. 384 * 385 * @param o The object to check. 386 * @return 387 * <jk>true</jk> if the specified object is a subclass of the normal class defined on this transform. 388 * <jk>null</jk> always return <jk>false</jk>. 389 */ 390 public boolean isNormalObject(Object o) { 391 if (o == null) 392 return false; 393 return normalClassInfo.isParentOf(o.getClass()); 394 } 395 396 /** 397 * Checks if the specified object is an instance of the swap class defined on this swap. 398 * 399 * @param o The object to check. 400 * @return 401 * <jk>true</jk> if the specified object is a subclass of the transformed class defined on this transform. 402 * <jk>null</jk> always return <jk>false</jk>. 403 */ 404 public boolean isSwappedObject(Object o) { 405 if (o == null) 406 return false; 407 return swapClassInfo.isParentOf(o.getClass()); 408 } 409 410 //----------------------------------------------------------------------------------------------------------------- 411 // Overridden methods 412 //----------------------------------------------------------------------------------------------------------------- 413 414 @Override /* Object */ 415 public String toString() { 416 return getClass().getSimpleName() + '<' + getNormalClass().getSimpleName() + "," + getSwapClass().getSimpleName() + '>'; 417 } 418}