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.objecttools;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.lang.reflect.*;
022
023import org.apache.juneau.commons.reflect.*;
024
025/**
026 * POJO merger.
027 *
028 * <p>
029 * Useful in cases where you want to define beans with 'default' values.
030 *
031 * <p>
032 * For example, given the following bean classes...
033 *
034 * <p class='bjava'>
035 *    <jk>public interface</jk> IA {
036 *       String getX();
037 *       <jk>void</jk> setX(String <jv>x</jv>);
038 *    }
039 *
040 *    <jk>public class</jk> A <jk>implements</jk> IA {
041 *       <jk>private</jk> String <jf>x</jf>;
042 *
043 *       <jk>public</jk> A(String <jv>x</jv>) {
044 *          <jk>this</jk>.<jf>x</jf> = <jv>x</jv>;
045 *       }
046 *
047 *       <jk>public</jk> String getX() {
048 *          <jk>return</jk> <jf>x</jf>;
049 *       }
050 *
051 *       <jk>public void</jk> setX(String <jv>x</jv>) {
052 *          <jk>this</jk>.<jf>x</jf> = <jv>x</jv>;
053 *       }
054 *    }
055 * </p>
056 *
057 * <p>
058 * The getters will be called in order until the first non-null value is returned...
059 *
060 * <p class='bjava'>
061 *    <jv>merge</jv> = ObjectMerger.<jsm>merge</jsm>(IA.<jk>class</jk>, <jk>new</jk> A(<js>"1"</js>), <jk>new</jk> A(<js>"2"</js>));
062 *    <jsm>assertEquals</jsm>(<js>"1"</js>, <jv>merge</jv>.getX());
063 *
064 *    <jv>merge</jv> = ObjectMerger.<jsm>merge</jsm>(IA.<jk>class</jk>, <jk>new</jk> A(<jk>null</jk>), <jk>new</jk> A(<js>"2"</js>));
065 *    <jsm>assertEquals</jsm>(<js>"2"</js>, <jv>merge</jv>.getX());
066 *
067 *    <jv>merge</jv> = ObjectMerger.<jsm>merge</jsm>(IA.<jk>class</jk>, <jk>new</jk> A(<jk>null</jk>), <jk>new</jk> A(<jk>null</jk>));
068 *    <jsm>assertEquals</jsm>(<jk>null</jk>, <jv>merge</jv>.getX());
069 * </p>
070 *
071 * <h5 class='section'>Notes:</h5><ul>
072 *    <li class='note'>
073 *       Null POJOs are ignored.
074 *    <li class='note'>
075 *       Non-getter methods are either invoked on the first POJO or all POJOs depending on the <c>callAllNonGetters</c> flag
076 *       passed into the constructor.
077 *    <li class='note'>
078 *       For purposes of this interface, a getter is any method with zero arguments and a non-<c>void</c> return type.
079 * </ul>
080 *
081 */
082@SuppressWarnings({
083   "unchecked" // Type erasure requires cast to T for proxy creation
084})
085public class ObjectMerger {
086
087   /**
088    * Prevents instantiation.
089    */
090   private ObjectMerger() {}
091
092   private static class MergeInvocationHandler implements InvocationHandler {
093      private final Object[] pojos;
094      private final boolean callAllNonGetters;
095
096      /**
097       * Constructor.
098       *
099       * @param callAllNonGetters If <jk>true</jk>, non-getter methods are invoked on all POJOs; if <jk>false</jk>,
100       *    only on the first POJO.
101       * @param pojos The POJOs to merge, in priority order.
102       */
103      public MergeInvocationHandler(boolean callAllNonGetters, Object...pojos) {
104         this.callAllNonGetters = callAllNonGetters;
105         this.pojos = pojos;
106      }
107
108      /**
109       * Implemented to handle the method called.
110       * @throws ExecutableException Exception occurred on invoked constructor/method/field.
111       */
112      @Override /* Overridden from InvocationHandler */
113      @SuppressWarnings({
114         "java:S3776" // Cognitive complexity acceptable for proxy method invocation routing
115      })
116      public Object invoke(Object proxy, Method method, Object[] args) throws ExecutableException {
117         Object r = null;
118         var isGetter = args == null && method.getReturnType() != Void.class;
119         for (var pojo : pojos) {
120            if (nn(pojo)) {
121               try {
122                  r = method.invoke(pojo, args);
123               } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
124                  throw new ExecutableException(e);
125               }
126               if (isGetter) {
127                  if (nn(r))
128                     return r;
129               } else {
130                  if (! callAllNonGetters)
131                     return r;
132               }
133            }
134         }
135         return r;
136      }
137   }
138
139   /**
140    * Create a proxy interface on top of zero or more POJOs.
141    *
142    * @param <T> The pojo types.
143    * @param interfaceClass The common interface class.
144    * @param callAllNonGetters
145    *    If <jk>true</jk>, when calling a method that's not a getter, the method will be invoked on all POJOs.
146    *    <br>Otherwise, the method will only be called on the first POJO.
147    * @param pojos
148    *    Zero or more POJOs to merge.
149    *    <br>Can contain nulls.
150    * @return A proxy interface over the merged POJOs.
151    */
152   public static <T> T merge(Class<T> interfaceClass, boolean callAllNonGetters, T...pojos) {
153      return (T)Proxy.newProxyInstance(interfaceClass.getClassLoader(), a(interfaceClass), new MergeInvocationHandler(callAllNonGetters, pojos));
154   }
155
156   /**
157    * Create a proxy interface on top of zero or more POJOs.
158    *
159    * <p>
160    * This is a shortcut to calling <code>merge(interfaceClass, <jk>false</jk>, pojos);</code>
161    *
162    * @param <T> The pojo types.
163    * @param interfaceClass The common interface class.
164    * @param pojos
165    *    Zero or more POJOs to merge.
166    *    <br>Can contain nulls.
167    * @return A proxy interface over the merged POJOs.
168    */
169   public static <T> T merge(Class<T> interfaceClass, T...pojos) {
170      return merge(interfaceClass, false, pojos);
171   }
172}