001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.objecttools;
014
015import java.lang.reflect.*;
016
017import org.apache.juneau.ExecutableException;
018
019/**
020 * POJO merger.
021 *
022 * <p>
023 * Useful in cases where you want to define beans with 'default' values.
024 *
025 * <p>
026 * For example, given the following bean classes...
027 *
028 * <p class='bjava'>
029 *    <jk>public interface</jk> IA {
030 *       String getX();
031 *       <jk>void</jk> setX(String <jv>x</jv>);
032 *    }
033 *
034 *    <jk>public class</jk> A <jk>implements</jk> IA {
035 *       <jk>private</jk> String <jf>x</jf>;
036 *
037 *       <jk>public</jk> A(String <jv>x</jv>) {
038 *          <jk>this</jk>.<jf>x</jf> = <jv>x</jv>;
039 *       }
040 *
041 *       <jk>public</jk> String getX() {
042 *          <jk>return</jk> <jf>x</jf>;
043 *       }
044 *
045 *       <jk>public void</jk> setX(String <jv>x</jv>) {
046 *          <jk>this</jk>.<jf>x</jf> = <jv>x</jv>;
047 *       }
048 *    }
049 * </p>
050 *
051 * <p>
052 * The getters will be called in order until the first non-null value is returned...
053 *
054 * <p class='bjava'>
055 *    <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>));
056 *    <jsm>assertEquals</jsm>(<js>"1"</js>, <jv>merge</jv>.getX());
057 *
058 *    <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>));
059 *    <jsm>assertEquals</jsm>(<js>"2"</js>, <jv>merge</jv>.getX());
060 *
061 *    <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>));
062 *    <jsm>assertEquals</jsm>(<jk>null</jk>, <jv>merge</jv>.getX());
063 * </p>
064 *
065 * <h5 class='section'>Notes:</h5><ul>
066 *    <li class='note'>
067 *       Null POJOs are ignored.
068 *    <li class='note'>
069 *       Non-getter methods are either invoked on the first POJO or all POJOs depending on the <c>callAllNonGetters</c> flag
070 *       passed into the constructor.
071 *    <li class='note'>
072 *       For purposes of this interface, a getter is any method with zero arguments and a non-<c>void</c> return type.
073 * </ul>
074 *
075 * <h5 class='section'>See Also:</h5><ul>
076 * </ul>
077 */
078public class ObjectMerger {
079
080   /**
081    * Create a proxy interface on top of zero or more POJOs.
082    *
083    * <p>
084    * This is a shortcut to calling <code>merge(interfaceClass, <jk>false</jk>, pojos);</code>
085    *
086    * @param <T> The pojo types.
087    * @param interfaceClass The common interface class.
088    * @param pojos
089    *    Zero or more POJOs to merge.
090    *    <br>Can contain nulls.
091    * @return A proxy interface over the merged POJOs.
092    */
093   @SuppressWarnings("unchecked")
094   public static <T> T merge(Class<T> interfaceClass, T...pojos) {
095      return merge(interfaceClass, false, pojos);
096   }
097
098   /**
099    * Create a proxy interface on top of zero or more POJOs.
100    *
101    * @param <T> The pojo types.
102    * @param interfaceClass The common interface class.
103    * @param callAllNonGetters
104    *    If <jk>true</jk>, when calling a method that's not a getter, the method will be invoked on all POJOs.
105    *    <br>Otherwise, the method will only be called on the first POJO.
106    * @param pojos
107    *    Zero or more POJOs to merge.
108    *    <br>Can contain nulls.
109    * @return A proxy interface over the merged POJOs.
110    */
111   @SuppressWarnings("unchecked")
112   public static <T> T merge(Class<T> interfaceClass, boolean callAllNonGetters, T...pojos) {
113      return (T)Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[] { interfaceClass }, new MergeInvocationHandler(callAllNonGetters, pojos));
114   }
115
116   private static class MergeInvocationHandler implements InvocationHandler {
117      private final Object[] pojos;
118      private final boolean callAllNonGetters;
119
120      public MergeInvocationHandler(boolean callAllNonGetters, Object...pojos) {
121         this.callAllNonGetters = callAllNonGetters;
122         this.pojos = pojos;
123      }
124
125      /**
126       * Implemented to handle the method called.
127       * @throws ExecutableException Exception occurred on invoked constructor/method/field.
128       */
129      @Override /* InvocationHandler */
130      public Object invoke(Object proxy, Method method, Object[] args) throws ExecutableException {
131         Object r = null;
132         boolean isGetter = args == null && method.getReturnType() != Void.class;
133         for (Object pojo : pojos) {
134            if (pojo != null) {
135               try {
136                  r = method.invoke(pojo, args);
137               } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
138                  throw new ExecutableException(e);
139               }
140               if (isGetter) {
141                  if (r != null)
142                     return r;
143               } else {
144                  if (! callAllNonGetters)
145                     return r;
146               }
147            }
148         }
149         return r;
150      }
151   }
152}