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