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}