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