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'> 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'> 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}