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