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.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 * <code>PojoSwaps</code> are used to extend the functionality of the serializers and parsers to be able to handle
033 * POJOs 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 * <code>PojoSwaps</code> are associated with serializers and parsers through the following:
044 * <ul>
045 *    <li class='ja'>{@link Swap @Swap} 
046 *    <li class='ja'>{@link Swaps @Swaps} 
047 *    <li class='jm'>{@link BeanContextBuilder#pojoSwaps(Object...)}
048 *    <li class='jm'>{@link BeanContextBuilder#pojoSwaps(Class...)}
049 *    <li class='jm'>{@link BeanContextBuilder#pojoSwaps(boolean, Object...)}
050 *    <li class='jm'>{@link BeanContextBuilder#pojoSwapsRemove(Object...)}
051 * </ul>
052 * 
053 * <p>
054 * <code>PojoSwaps</code> 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 * 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 * <h5 class='topic'>Normal Class Type {@code <T>}</h5>
088 * 
089 * <p>
090 * The normal object representation of an object.
091 * 
092 * <h5 class='section'>See Also:</h5>
093 * <ul>
094 *    <li class='link'><a class="doclink" href="../../../../overview-summary.html#juneau-marshall.PojoSwaps">Overview &gt; juneau-marshall &gt; PojoSwaps</a>
095 *    <li class='link'><a class="doclink" href="../../../../overview-summary.html#juneau-marshall.SwapAnnotation">Overview &gt; juneau-marshall &gt; @Swap Annotation</a>
096 * </ul>
097 * 
098 * @param <T> The normal form of the class.
099 * @param <S> The swapped form of the class.
100 */
101@SuppressWarnings({"unchecked","rawtypes"})
102@BeanIgnore
103public abstract class PojoSwap<T,S> {
104
105   /**
106    * Represents a non-existent pojo swap.
107    */
108   public static final PojoSwap NULL = new PojoSwap((Class)null, (Class)null) {};
109
110   private final Class<T> normalClass;
111   private final Class<?> swapClass;
112   private ClassMeta<?> swapClassMeta;
113
114   // Unfortunately these cannot be made final because we want to allow for PojoSwaps with no-arg constructors
115   // which simplifies declarations.
116   private MediaType[] forMediaTypes;
117   private String template;
118
119   /**
120    * Constructor.
121    */
122   protected PojoSwap() {
123      normalClass = (Class<T>)resolveParameterType(PojoSwap.class, 0, this.getClass());
124      swapClass = resolveParameterType(PojoSwap.class, 1, this.getClass());
125      forMediaTypes = forMediaTypes();
126      template = withTemplate();
127   }
128
129   /**
130    * Constructor for when the normal and transformed classes are already known.
131    * 
132    * @param normalClass The normal class (cannot be serialized).
133    * @param swapClass The transformed class (serializable).
134    */
135   protected PojoSwap(Class<T> normalClass, Class<?> swapClass) {
136      this.normalClass = normalClass;
137      this.swapClass = swapClass;
138      this.forMediaTypes = forMediaTypes();
139      this.template = withTemplate();
140   }
141
142   /**
143    * Returns the media types that this swap is applicable to.
144    * 
145    * <p>
146    * This method can be overridden to programmatically specify what media types it applies to.
147    * 
148    * <p>
149    * This method is the programmatic equivalent to the {@link Swap#mediaTypes() @Swap.mediaTypes()} annotation.
150    * 
151    * <h5 class='topic'>Documentation</h5>
152    * <ul>
153    *    <li class='link'><a class="doclink" href="../../../../overview-summary.html#juneau-marshall.PerMediaTypePojoSwaps">Overview &gt; juneau-marshall &gt; Per-media-type PojoSwaps</a>
154    * </ul>
155    * @return The media types that this swap is applicable to, or <jk>null</jk> if it's applicable for all media types.
156    */
157   public MediaType[] forMediaTypes() {
158      return null;
159   }
160
161   /**
162    * Returns additional context information for this swap.
163    * 
164    * <p>
165    * Typically this is going to be used to specify a template name, such as a FreeMarker template file name.
166    * 
167    * <p>
168    * This method can be overridden to programmatically specify a template value.
169    * 
170    * <p>
171    * This method is the programmatic equivalent to the {@link Swap#template() @Swap.template()} annotation.
172    * 
173    * <h5 class='section'>See Also:</h5>
174    * <ul>
175    *    <li class='link'><a class="doclink" href="../../../../overview-summary.html#juneau-marshall.TemplatedSwaps">Overview &gt; juneau-marshall &gt; Templated Swaps</a>
176    * </ul>
177    * 
178    * @return Additional context information, or <jk>null</jk> if not specified.
179    */
180   public String withTemplate() {
181      return null;
182   }
183
184   /**
185    * Sets the media types that this swap is associated with.
186    * 
187    * <h5 class='topic'>Documentation</h5>
188    * <ul>
189    *    <li class='link'><a class="doclink" href="../../../../overview-summary.html#juneau-marshall.PerMediaTypePojoSwaps">Overview &gt; juneau-marshall &gt; Per-media-type PojoSwaps</a>
190    * </ul>
191    * 
192    * @param mediaTypes The media types that this swap is associated with.
193    * @return This object (for method chaining).
194    */
195   public PojoSwap<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>
204    * <ul>
205    *    <li class='link'><a class="doclink" href="../../../../overview-summary.html#juneau-marshall.TemplatedSwaps">Overview &gt; juneau-marshall &gt; Templated Swaps</a>
206    * </ul>
207    * 
208    * @param template The template string on this swap.
209    * @return This object (for method chaining).
210    */
211   public PojoSwap<T,?> withTemplate(String template) {
212      this.template = template;
213      return this;
214   }
215
216   /**
217    * Returns a number indicating how well this swap matches the specified session.
218    * 
219    * <p>
220    * Uses the {@link MediaType#match(MediaType, boolean)} method algorithm to produce a number whereby a
221    * larger value indicates a "better match".
222    * The idea being that if multiple swaps are associated with a given POJO, we want to pick the best one.
223    * 
224    * <p>
225    * For example, if the session media type is <js>"text/json"</js>, then the match values are shown below:
226    * 
227    * <ul>
228    *    <li><js>"text/json"</js> = <code>100,000</code>
229    *    <li><js>"&#42;/json"</js> = <code>5,100</code>
230    *    <li><js>"&#42;/&#42;"</js> = <code>5,000</code>
231    *    <li>No media types specified on swap = <code>1</code>
232    *    <li><js>"text/xml"</js> = <code>0</code>
233    * </ul>
234    * 
235    * @param session The bean session.
236    * @return Zero if swap doesn't match the session, or a positive number if it does.
237    */
238   public int match(BeanSession session) {
239      if (forMediaTypes == null)
240         return 1;
241      int i = 0;
242      MediaType mt = session.getMediaType();
243      if (forMediaTypes != null)
244         for (MediaType mt2 : forMediaTypes)
245            i = Math.max(i, mt2.match(mt, false));
246      return i;
247   }
248
249   /**
250    * If this transform is to be used to serialize non-serializable POJOs, it must implement this method.
251    * 
252    * <p>
253    * The object must be converted into one of the following serializable types:
254    * <ul class='spaced-list'>
255    *    <li>
256    *       {@link String}
257    *    <li>
258    *       {@link Number}
259    *    <li>
260    *       {@link Boolean}
261    *    <li>
262    *       {@link Collection} containing anything on this list.
263    *    <li>
264    *       {@link Map} containing anything on this list.
265    *    <li>
266    *       A java bean with properties of anything on this list.
267    *    <li>
268    *       An array of anything on this list.
269    * </ul>
270    * 
271    * @param session
272    *    The bean session to use to get the class meta.
273    *    This is always going to be the same bean context that created this swap.
274    * @param o The object to be transformed.
275    * @return The transformed object.
276    * @throws Exception If a problem occurred trying to convert the output.
277    */
278   public S swap(BeanSession session, T o) throws Exception {
279      return swap(session, o, template);
280   }
281
282   /**
283    * Same as {@link #swap(BeanSession, Object)}, but can be used if your swap has a template associated with it.
284    * 
285    * @param session
286    *    The bean session to use to get the class meta.
287    *    This is always going to be the same bean context that created this swap.
288    * @param o The object to be transformed.
289    * @param template
290    *    The template string associated with this swap.
291    * @return The transformed object.
292    * @throws Exception If a problem occurred trying to convert the output.
293    */
294   public S swap(BeanSession session, T o, String template) throws Exception {
295      throw new SerializeException("Swap method not implemented on PojoSwap ''{0}''", this.getClass().getName());
296   }
297
298   /**
299    * If this transform is to be used to reconstitute POJOs that aren't true Java beans, it must implement this method.
300    * 
301    * @param session
302    *    The bean session to use to get the class meta.
303    *    This is always going to be the same bean context that created this swap.
304    * @param f The transformed object.
305    * @param hint
306    *    If possible, the parser will try to tell you the object type being created.
307    *    For example, on a serialized date, this may tell you that the object being created must be of type
308    *    {@code GregorianCalendar}.
309    *    <br>This may be <jk>null</jk> if the parser cannot make this determination.
310    * @return The narrowed object.
311    * @throws Exception If this method is not implemented.
312    */
313   public T unswap(BeanSession session, S f, ClassMeta<?> hint) throws Exception {
314      return unswap(session, f, hint, template);
315   }
316
317   /**
318    * Same as {@link #unswap(BeanSession, Object, ClassMeta)}, but can be used if your swap has a template associated with it.
319    * 
320    * @param session
321    *    The bean session to use to get the class meta.
322    *    This is always going to be the same bean context that created this swap.
323    * @param f The transformed object.
324    * @param hint
325    *    If possible, the parser will try to tell you the object type being created.
326    *    For example, on a serialized date, this may tell you that the object being created must be of type
327    *    {@code GregorianCalendar}.
328    *    <br>This may be <jk>null</jk> if the parser cannot make this determination.
329    * @param template
330    *    The template string associated with this swap.
331    * @return The transformed object.
332    * @throws Exception If a problem occurred trying to convert the output.
333    */
334   public T unswap(BeanSession session, S f, ClassMeta<?> hint, String template) throws Exception {
335      throw new ParseException("Unswap method not implemented on PojoSwap ''{0}''", this.getClass().getName());
336   }
337
338   /**
339    * Returns the T class, the normalized form of the class.
340    * 
341    * @return The normal form of this class.
342    */
343   public Class<T> getNormalClass() {
344      return normalClass;
345   }
346
347   /**
348    * Returns the G class, the generalized form of the class.
349    * 
350    * <p>
351    * Subclasses must override this method if the generalized class is {@code Object}, meaning it can produce multiple
352    * generalized forms.
353    * 
354    * @return The transformed form of this class.
355    */
356   public Class<?> getSwapClass() {
357      return swapClass;
358   }
359
360   /**
361    * Returns the {@link ClassMeta} of the transformed class type.
362    * 
363    * <p>
364    * This value is cached for quick lookup.
365    * 
366    * @param session
367    *    The bean context to use to get the class meta.
368    *    This is always going to be the same bean context that created this swap.
369    * @return The {@link ClassMeta} of the transformed class type.
370    */
371   public ClassMeta<?> getSwapClassMeta(BeanSession session) {
372      if (swapClassMeta == null)
373         swapClassMeta = session.getClassMeta(swapClass);
374      return swapClassMeta;
375   }
376
377   /**
378    * Checks if the specified object is an instance of the normal class defined on this swap.
379    * 
380    * @param o The object to check.
381    * @return
382    *    <jk>true</jk> if the specified object is a subclass of the normal class defined on this transform.
383    *    <jk>null</jk> always return <jk>false</jk>.
384    */
385   public boolean isNormalObject(Object o) {
386      if (o == null)
387         return false;
388      return isParentClass(normalClass, o.getClass());
389   }
390
391   /**
392    * Checks if the specified object is an instance of the swap class defined on this swap.
393    * 
394    * @param o The object to check.
395    * @return
396    *    <jk>true</jk> if the specified object is a subclass of the transformed class defined on this transform.
397    *    <jk>null</jk> always return <jk>false</jk>.
398    */
399   public boolean isSwappedObject(Object o) {
400      if (o == null)
401         return false;
402      return isParentClass(swapClass, o.getClass());
403   }
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}