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}