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