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