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