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