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