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