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 017import org.apache.juneau.ExecutableException; 018 019/** 020 * Utility class for merging POJOs behind a single interface. 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='bcode w800'> 029 * <jk>public interface</jk> IA { 030 * String getX(); 031 * <jk>void</jk> setX(String x); 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 x) { 038 * <jk>this</jk>.<jf>x</jf> = x; 039 * } 040 * 041 * <jk>public</jk> String getX() { 042 * <jk>return</jk> <jf>x</jf>; 043 * } 044 * 045 * <jk>public void</jk> setX(String x) { 046 * <jk>this</jk>.<jf>x</jf> = x; 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='bcode w800'> 055 * PojoMerge m; 056 * 057 * m = PojoMerge.<jsm>merge</jsm>(IA.<jk>class</jk>, <jk>new</jk> A(<js>"1"</js>), <jk>new</jk> A(<js>"2"</js>)); 058 * <jsm>assertEquals</jsm>(<js>"1"</js>, m.getX()); 059 * 060 * m = PojoMerge.<jsm>merge</jsm>(IA.<jk>class</jk>, <jk>new</jk> A(<jk>null</jk>), <jk>new</jk> A(<js>"2"</js>)); 061 * <jsm>assertEquals</jsm>(<js>"2"</js>, m.getX()); 062 * 063 * m = PojoMerge.<jsm>merge</jsm>(IA.<jk>class</jk>, <jk>new</jk> A(<jk>null</jk>), <jk>new</jk> A(<jk>null</jk>)); 064 * <jsm>assertEquals</jsm>(<jk>null</jk>, m.getX()); 065 * </p> 066 * 067 * <ul class='notes'> 068 * <li> 069 * Null POJOs are ignored. 070 * <li> 071 * Non-getter methods are either invoked on the first POJO or all POJOs depending on the <c>callAllNonGetters</c> flag 072 * passed into the constructor. 073 * <li> 074 * For purposes of this interface, a getter is any method with zero arguments and a non-<c>void</c> return type. 075 * </ul> 076 */ 077public class PojoMerge { 078 079 /** 080 * Create a proxy interface on top of zero or more POJOs. 081 * 082 * <p> 083 * This is a shortcut to calling <code>merge(interfaceClass, <jk>false</jk>, pojos);</code> 084 * 085 * @param interfaceClass The common interface class. 086 * @param pojos 087 * Zero or more POJOs to merge. 088 * <br>Can contain nulls. 089 * @return A proxy interface over the merged POJOs. 090 */ 091 @SuppressWarnings("unchecked") 092 public static <T> T merge(Class<T> interfaceClass, T...pojos) { 093 return merge(interfaceClass, false, pojos); 094 } 095 096 /** 097 * Create a proxy interface on top of zero or more POJOs. 098 * 099 * @param interfaceClass The common interface class. 100 * @param callAllNonGetters 101 * If <jk>true</jk>, when calling a method that's not a getter, the method will be invoked on all POJOs. 102 * <br>Otherwise, the method will only be called on the first POJO. 103 * @param pojos 104 * Zero or more POJOs to merge. 105 * <br>Can contain nulls. 106 * @return A proxy interface over the merged POJOs. 107 */ 108 @SuppressWarnings("unchecked") 109 public static <T> T merge(Class<T> interfaceClass, boolean callAllNonGetters, T...pojos) { 110 return (T)Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[] { interfaceClass }, new PojoMergeInvocationHandler(callAllNonGetters, pojos)); 111 } 112 113 private static class PojoMergeInvocationHandler implements InvocationHandler { 114 private final Object[] pojos; 115 private final boolean callAllNonGetters; 116 117 public PojoMergeInvocationHandler(boolean callAllNonGetters, Object...pojos) { 118 this.callAllNonGetters = callAllNonGetters; 119 this.pojos = pojos; 120 } 121 122 /** 123 * Implemented to handle the method called. 124 * @throws ExecutableException Exception occurred on invoked constructor/method/field. 125 */ 126 @Override /* InvocationHandler */ 127 public Object invoke(Object proxy, Method method, Object[] args) throws ExecutableException { 128 Object r = null; 129 boolean isGetter = args == null && method.getReturnType() != Void.class; 130 for (Object pojo : pojos) { 131 if (pojo != null) { 132 try { 133 r = method.invoke(pojo, args); 134 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 135 throw new ExecutableException(e); 136 } 137 if (isGetter) { 138 if (r != null) 139 return r; 140 } else { 141 if (! callAllNonGetters) 142 return r; 143 } 144 } 145 } 146 return r; 147 } 148 } 149}