| // *************************************************************************************************************************** |
| // * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * |
| // * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * |
| // * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * |
| // * with the License. You may obtain a copy of the License at * |
| // * * |
| // * http://www.apache.org/licenses/LICENSE-2.0 * |
| // * * |
| // * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * |
| // * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * |
| // * specific language governing permissions and limitations under the License. * |
| // *************************************************************************************************************************** |
| package org.apache.juneau.utils; |
| |
| import java.util.*; |
| |
| import org.apache.juneau.*; |
| import org.apache.juneau.annotation.Bean; |
| import org.apache.juneau.internal.*; |
| import org.apache.juneau.marshall.SimpleJson; |
| |
| /** |
| * Utility class for comparing two versions of a POJO. |
| * |
| * <p class='bpcode w800'> |
| * <jc>// Two beans to compare.</jc> |
| * MyBean bean1, bean2; |
| * |
| * <jc>// Get differences.</jc> |
| * BeanDiff bf = BeanDiff.<jsm>create</jsm>(bean1, bean2).exclude(<js>"fooProperty"</js>).build(); |
| * |
| * <jc>// Check for differences.</jc> |
| * <jk>boolean</jk> b = bf.hasDiffs(); |
| * |
| * ObjectMap v1 = bf.getV1(); <jc>// Get version 1 differences.</jc> |
| * ObjectMap v2 = bf.getV2(); <jc>// Get version 2 differences.</jc> |
| * |
| * <jc>// Display differences.</jc> |
| * System.<jsf>err</jsf>.println(bf); |
| * </p> |
| */ |
| @Bean(properties="v1,v2") |
| public class BeanDiff { |
| |
| private ObjectMap v1 = new ObjectMap(), v2 = new ObjectMap(); |
| |
| /** |
| * Constructor. |
| * |
| * @param bc The bean context to use for comparing beans. |
| * @param first The first bean to compare. |
| * @param second The second bean to compare. |
| * @param include |
| * Optional properties to include in the comparison. |
| * <br>If <jk>null</jk>, all properties are included. |
| * @param exclude |
| * Optional properties to exclude in the comparison. |
| * <br>If <jk>null</jk>, no properties are excluded. |
| */ |
| @SuppressWarnings("null") |
| public <T> BeanDiff(BeanContext bc, T first, T second, Set<String> include, Set<String> exclude) { |
| if (first == null && second == null) |
| return; |
| BeanSession bs = bc.createBeanSession(); |
| BeanMap<?> bm1 = first == null ? null : bs.toBeanMap(first); |
| BeanMap<?> bm2 = second == null ? null : bs.toBeanMap(second); |
| Set<String> keys = bm1 != null ? bm1.keySet() : bm2.keySet(); |
| for (String k : keys) { |
| if ((include == null || include.contains(k)) && (exclude == null || ! exclude.contains(k))) { |
| Object o1 = bm1 == null ? null : bm1.get(k); |
| Object o2 = bm2 == null ? null : bm2.get(k); |
| if (! ObjectUtils.equals(o1, o2)) { |
| if (o1 != null) |
| v1.put(k, o1); |
| if (o2 != null) |
| v2.put(k, o2); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Create a new builder for this class. |
| * |
| * @param first The first bean to compare. |
| * @param second The second bean to compare. |
| * @return A new builder. |
| */ |
| public static <T> Builder<T> create(T first, T second) { |
| return new Builder<T>().first(first).second(second); |
| } |
| |
| /** |
| * Builder class. |
| * |
| * @param <T> The bean type. |
| */ |
| public static class Builder<T> { |
| T first, second; |
| BeanContext beanContext = BeanContext.DEFAULT; |
| Set<String> include, exclude; |
| |
| /** |
| * Specifies the first bean to compare. |
| * |
| * @param value The first bean to compare. |
| * @return This object (for method chaining). |
| */ |
| public Builder<T> first(T value) { |
| this.first = value; |
| return this; |
| } |
| |
| /** |
| * Specifies the second bean to compare. |
| * |
| * @param value The first bean to compare. |
| * @return This object (for method chaining). |
| */ |
| public Builder<T> second(T value) { |
| this.second = value; |
| return this; |
| } |
| |
| /** |
| * Specifies the bean context to use for introspecting beans. |
| * |
| * <p> |
| * If not specified, uses {@link BeanContext#DEFAULT}. |
| * |
| * @param value The bean context to use for introspecting beans. |
| * @return This object (for method chaining). |
| */ |
| public Builder<T> beanContext(BeanContext value) { |
| this.beanContext = value; |
| return this; |
| } |
| |
| /** |
| * Specifies the properties to include in the comparison. |
| * |
| * <p> |
| * If not specified, compares all properties. |
| * |
| * @param properties The properties to include in the comparison. |
| * @return This object (for method chaining). |
| */ |
| public Builder<T> include(String...properties) { |
| include = new HashSet<>(Arrays.asList(properties)); |
| return this; |
| } |
| |
| /** |
| * Specifies the properties to include in the comparison. |
| * |
| * <p> |
| * If not specified, compares all properties. |
| * |
| * @param properties The properties to include in the comparison. |
| * @return This object (for method chaining). |
| */ |
| public Builder<T> include(Set<String> properties) { |
| include = properties; |
| return this; |
| } |
| |
| /** |
| * Specifies the properties to exclude from the comparison. |
| * |
| * @param properties The properties to exclude from the comparison. |
| * @return This object (for method chaining). |
| */ |
| public Builder<T> exclude(String...properties) { |
| exclude = new HashSet<>(Arrays.asList(properties)); |
| return this; |
| } |
| |
| /** |
| * Specifies the properties to exclude from the comparison. |
| * |
| * @param properties The properties to exclude from the comparison. |
| * @return This object (for method chaining). |
| */ |
| public Builder<T> exclude(Set<String> properties) { |
| exclude = properties; |
| return this; |
| } |
| |
| /** |
| * Build the differences. |
| * |
| * @return A new {@link BeanDiff} object. |
| */ |
| public BeanDiff build() { |
| return new BeanDiff(beanContext, first, second, include, exclude); |
| } |
| |
| } |
| |
| /** |
| * Returns <jk>true</jk> if the beans had differences. |
| * |
| * @return <jk>true</jk> if the beans had differences. |
| */ |
| public boolean hasDiffs() { |
| return v1.size() > 0 || v2.size() > 0; |
| } |
| |
| /** |
| * Returns the differences in the first bean. |
| * |
| * @return The differences in the first bean. |
| */ |
| public ObjectMap getV1() { |
| return v1; |
| } |
| |
| /** |
| * Returns the differences in the second bean. |
| * |
| * @return The differences in the second bean. |
| */ |
| public ObjectMap getV2() { |
| return v2; |
| } |
| |
| @Override |
| public String toString() { |
| return SimpleJson.DEFAULT.toString(this); |
| } |
| } |