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.commons.reflect.ReflectionUtils.*;
020import static org.apache.juneau.commons.utils.Utils.*;
021
022import java.lang.reflect.*;
023import java.util.*;
024
025import org.apache.juneau.*;
026import org.apache.juneau.annotation.*;
027import org.apache.juneau.commons.reflect.*;
028import org.apache.juneau.parser.*;
029import org.apache.juneau.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/SwapBasics">Swap Basics</a>
036 * </ul>
037 *
038 * @param <T> The class type that this transform applies to.
039 * @param <F> The transformed class type.
040 */
041public class SurrogateSwap<T,F> extends ObjectSwap<T,F> {
042
043   /**
044    * Given the specified surrogate class, return the list of object swaps.
045    *
046    * <p>
047    * A transform is returned for each public 1-arg constructor found.
048    * Returns an empty list if no public 1-arg constructors are found.
049    *
050    * @param c The surrogate class.
051    * @param bc The bean context to use for looking up annotations.
052    * @return The list of object swaps that apply to this class.
053    */
054   @SuppressWarnings({ "unchecked", "rawtypes" })
055   public static List<SurrogateSwap<?,?>> findObjectSwaps(Class<?> c, BeanContext bc) {
056      List<SurrogateSwap<?,?>> l = new LinkedList<>();
057      var ci = info(c);
058      ci.getPublicConstructors().stream().filter(x -> ! bc.getAnnotationProvider().has(BeanIgnore.class, x) && x.hasNumParameters(1) && x.isPublic()).forEach(x -> {
059         var pt = x.getParameter(0).getParameterType().inner();
060         if (! pt.equals(c.getDeclaringClass())) {
061            // Find the unswap method if there is one.
062            Method unswapMethod = ci.getPublicMethod(y -> y.hasReturnType(pt)).map(MethodInfo::inner).orElse(null);
063            l.add(new SurrogateSwap(pt, x.inner(), unswapMethod));
064         }
065      });
066      return l;
067   }
068
069   private Constructor<F> constructor;   // public F(T t);
070
071   private Method unswapMethod;        // public T build();
072
073   /**
074    * Constructor.
075    *
076    * @param forClass The normal class.
077    * @param constructor The constructor on the surrogate class that takes the normal class as a parameter.
078    * @param unswapMethod The static method that converts surrogate objects into normal objects.
079    */
080   protected SurrogateSwap(Class<T> forClass, Constructor<F> constructor, Method unswapMethod) {
081      super(forClass, constructor.getDeclaringClass());
082      this.constructor = constructor;
083      this.unswapMethod = unswapMethod;
084   }
085
086   @Override /* Overridden from ObjectSwap */
087   public F swap(BeanSession session, T o) throws SerializeException {
088      try {
089         return constructor.newInstance(o);
090      } catch (Exception e) {
091         throw new SerializeException(e);
092      }
093   }
094
095   @Override /* Overridden from ObjectSwap */
096   @SuppressWarnings("unchecked")
097   public T unswap(BeanSession session, F f, ClassMeta<?> hint) throws ParseException {
098      if (unswapMethod == null)
099         throw new ParseException("unswap() method not implement on surrogate class ''{1}'': {0}", cn(f), getNormalClass().getNameFull());
100      try {
101         return (T)unswapMethod.invoke(f);
102      } catch (Exception e) {
103         throw new ParseException(e);
104      }
105   }
106}