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