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