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.marshall.swap.spi;
018
019import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021
022import java.lang.reflect.*;
023import java.util.*;
024
025import org.apache.juneau.commons.bean.*;
026import org.apache.juneau.commons.reflect.*;
027import org.apache.juneau.marshall.*;
028import org.apache.juneau.marshall.parser.*;
029import org.apache.juneau.marshall.serializer.*;
030
031/**
032 * Specialized {@link ObjectSwap} for {@link Surrogate} classes.
033 *
034 * <h5 class='section'>See Also:</h5><ul>
035 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/Swaps">Swap</a>
036 * </ul>
037 *
038 * @param <T> The class type that this transform applies to.
039 * @param <F> The transformed class type.
040 */
041@SuppressWarnings({
042   "unchecked" // unswap() casts the result of the reflective unswap-method invocation to T; the method is selected to return the surrogate's normal class
043})
044public class SurrogateSwap<T,F> extends ObjectSwap<T,F> {
045
046   /**
047    * Given the specified surrogate class, return the list of object swaps.
048    *
049    * <p>
050    * A transform is returned for each public 1-arg constructor found.
051    * Returns an empty list if no public 1-arg constructors are found.
052    *
053    * @param c The surrogate class. Must not be <jk>null</jk>.
054    * @param bc The bean context to use for looking up annotations. Must not be <jk>null</jk>.
055    * @return The list of object swaps that apply to this class. Never <jk>null</jk>.
056    */
057   @SuppressWarnings({
058      "java:S1452", // Wildcard required - List<SurrogateSwap<?,?>> for multiple constructor-based swaps
059      "rawtypes" // Raw types necessary for generic type handling
060   })
061   public static List<SurrogateSwap<?,?>> findObjectSwaps(Class<?> c, MarshallingContext bc) {
062      List<SurrogateSwap<?,?>> l = ll();
063      var ci = info(c);
064      ci.getPublicConstructors().stream().filter(x -> ! bc.getAnnotationProvider().has(BeanIgnore.class, x) && x.hasNumParameters(1) && x.isPublic()).forEach(x -> {
065         var pt = x.getParameter(0).getParameterType().inner();
066         if (! pt.equals(c.getDeclaringClass())) {
067            // Find the unswap method if there is one.
068            Method unswapMethod = ci.getPublicMethod(y -> y.hasReturnType(pt)).map(MethodInfo::inner).orElse(null);
069            l.add(new SurrogateSwap(pt, x.inner(), unswapMethod));
070         }
071      });
072      return l;
073   }
074
075   private final Constructor<F> constructor;
076
077   private final Method unswapMethod;
078
079   /**
080    * Constructor.
081    *
082    * @param forClass The normal class.
083    * @param constructor The constructor on the surrogate class that takes the normal class as a parameter. Must not be <jk>null</jk>.
084    * @param unswapMethod
085    *    The method on the surrogate class that converts surrogate objects back into normal objects.
086    *    Can be <jk>null</jk> if the surrogate is serialize-only (calling {@link #unswap(MarshallingSession, Object, ClassMeta)} then throws a {@link ParseException}).
087    */
088   protected SurrogateSwap(Class<T> forClass, Constructor<F> constructor, Method unswapMethod) {
089      super(forClass, constructor.getDeclaringClass());
090      this.constructor = constructor;
091      this.unswapMethod = unswapMethod;
092   }
093
094   @Override /* Overridden from ObjectSwap */
095   public F swap(MarshallingSession session, T o) throws SerializeException {
096      try {
097         return constructor.newInstance(o);
098      } catch (Exception e) {
099         throw new SerializeException(e);
100      }
101   }
102
103   @Override /* Overridden from ObjectSwap */
104   public T unswap(MarshallingSession session, F f, ClassMeta<?> hint) throws ParseException {
105      if (unswapMethod == null)
106         throw new ParseException("unswap() method not implement on surrogate class '%2$s': %1$s", cn(f), getNormalClass().getNameFull());
107      try {
108         return (T)unswapMethod.invoke(f);
109      } catch (Exception e) {
110         throw new ParseException(e);
111      }
112   }
113}