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.utils; 018 019import static org.apache.juneau.commons.utils.CollectionUtils.*; 020import static org.apache.juneau.commons.utils.Utils.*; 021 022import java.util.*; 023 024import org.apache.juneau.*; 025import org.apache.juneau.collections.*; 026import org.apache.juneau.commons.collections.*; 027 028/** 029 * Utility class for comparing two versions of a POJO. 030 * 031 * <p> 032 * 033 * <p class='bjava'> 034 * <jc>// Two beans to compare.</jc> 035 * MyBean <jv>bean1</jv>, <jv>bean2</jv>; 036 * 037 * <jc>// Get differences.</jc> 038 * BeanDiff <jv>beanDiff</jv> = BeanDiff.<jsm>create</jsm>(<jv>bean1</jv>, <jv>bean2</jv>).exclude(<js>"fooProperty"</js>).build(); 039 * 040 * <jc>// Check for differences.</jc> 041 * <jk>boolean</jk> <jv>hasDiff</jv> = <jv>beanDiff</jv>.hasDiffs(); 042 * 043 * JsonMap <jv>v1Diffs</jv> = <jv>beanDiff</jv>.getV1(); <jc>// Get version 1 differences.</jc> 044 * JsonMap <jv>v2Diffs</jv> = <jv>beanDiff</jv>.getV2(); <jc>// Get version 2 differences.</jc> 045 * 046 * <jc>// Display differences.</jc> 047 * System.<jsf>err</jsf>.println(<jv>beanDiff</jv>); 048 * </p> 049 * 050 */ 051public class BeanDiff { 052 053 /** 054 * Builder class. 055 * 056 * @param <T> The bean type. 057 */ 058 public static class Builder<T> { 059 T first, second; 060 BeanContext beanContext = BeanContext.DEFAULT; 061 Set<String> include, exclude; 062 063 /** 064 * Specifies the bean context to use for introspecting beans. 065 * 066 * <p> 067 * If not specified, uses {@link BeanContext#DEFAULT}. 068 * 069 * @param value The bean context to use for introspecting beans. 070 * @return This object. 071 */ 072 public Builder<T> beanContext(BeanContext value) { 073 beanContext = value; 074 return this; 075 } 076 077 /** 078 * Build the differences. 079 * 080 * @return A new {@link BeanDiff} object. 081 */ 082 public BeanDiff build() { 083 return new BeanDiff(beanContext, first, second, include, exclude); 084 } 085 086 /** 087 * Specifies the properties to exclude from the comparison. 088 * 089 * @param properties The properties to exclude from the comparison. 090 * @return This object. 091 */ 092 public Builder<T> exclude(Set<String> properties) { 093 exclude = properties; 094 return this; 095 } 096 097 /** 098 * Specifies the properties to exclude from the comparison. 099 * 100 * @param properties The properties to exclude from the comparison. 101 * @return This object. 102 */ 103 public Builder<T> exclude(String...properties) { 104 exclude = set(properties); 105 return this; 106 } 107 108 /** 109 * Specifies the first bean to compare. 110 * 111 * @param value The first bean to compare. 112 * @return This object. 113 */ 114 public Builder<T> first(T value) { 115 first = value; 116 return this; 117 } 118 119 /** 120 * Specifies the properties to include in the comparison. 121 * 122 * <p> 123 * If not specified, compares all properties. 124 * 125 * @param properties The properties to include in the comparison. 126 * @return This object. 127 */ 128 public Builder<T> include(Set<String> properties) { 129 include = properties; 130 return this; 131 } 132 133 /** 134 * Specifies the properties to include in the comparison. 135 * 136 * <p> 137 * If not specified, compares all properties. 138 * 139 * @param properties The properties to include in the comparison. 140 * @return This object. 141 */ 142 public Builder<T> include(String...properties) { 143 include = set(properties); 144 return this; 145 } 146 147 /** 148 * Specifies the second bean to compare. 149 * 150 * @param value The first bean to compare. 151 * @return This object. 152 */ 153 public Builder<T> second(T value) { 154 second = value; 155 return this; 156 } 157 } 158 159 /** 160 * Create a new builder for this class. 161 * 162 * @param <T> The bean types. 163 * @param first The first bean to compare. 164 * @param second The second bean to compare. 165 * @return A new builder. 166 */ 167 public static <T> Builder<T> create(T first, T second) { 168 return new Builder<T>().first(first).second(second); 169 } 170 171 private JsonMap v1 = new JsonMap(), v2 = new JsonMap(); 172 173 /** 174 * Constructor. 175 * 176 * @param <T> The bean types. 177 * @param bc The bean context to use for comparing beans. 178 * @param first The first bean to compare. 179 * @param second The second bean to compare. 180 * @param include 181 * Optional properties to include in the comparison. 182 * <br>If <jk>null</jk>, all properties are included. 183 * @param exclude 184 * Optional properties to exclude in the comparison. 185 * <br>If <jk>null</jk>, no properties are excluded. 186 */ 187 @SuppressWarnings("null") 188 public <T> BeanDiff(BeanContext bc, T first, T second, Set<String> include, Set<String> exclude) { 189 if (first == null && second == null) 190 return; 191 var bm1 = first == null ? null : bc.toBeanMap(first); 192 var bm2 = second == null ? null : bc.toBeanMap(second); 193 var keys = nn(bm1) ? bm1.keySet() : bm2.keySet(); 194 keys.forEach(k -> { 195 if ((include == null || include.contains(k)) && (exclude == null || ! exclude.contains(k))) { 196 var o1 = bm1 == null ? null : bm1.get(k); 197 var o2 = bm2 == null ? null : bm2.get(k); 198 if (neq(o1, o2)) { 199 if (nn(o1)) 200 v1.put(k, o1); 201 if (nn(o2)) 202 v2.put(k, o2); 203 } 204 } 205 }); 206 } 207 208 /** 209 * Returns the differences in the first bean. 210 * 211 * @return The differences in the first bean. 212 */ 213 public JsonMap getV1() { return v1; } 214 215 /** 216 * Returns the differences in the second bean. 217 * 218 * @return The differences in the second bean. 219 */ 220 public JsonMap getV2() { return v2; } 221 222 /** 223 * Returns <jk>true</jk> if the beans had differences. 224 * 225 * @return <jk>true</jk> if the beans had differences. 226 */ 227 public boolean hasDiffs() { 228 return v1.size() > 0 || v2.size() > 0; 229 } 230 231 protected FluentMap<String,Object> properties() { 232 // @formatter:off 233 return mapb_so().buildFluent() 234 .a("v1", v1) 235 .a("v2", v2); 236 // @formatter:on 237 } 238 239 @Override 240 public String toString() { 241 return r(properties()); 242 } 243}