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>"&#42;/json"</js> = <c>5,100</c>
233    *    <li><js>"&#42;/&#42;"</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}