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 * https://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.commons.lang3.builder; 018 019import java.lang.reflect.Field; 020import java.lang.reflect.Modifier; 021import java.util.Arrays; 022import java.util.HashSet; 023import java.util.Objects; 024import java.util.Set; 025 026import org.apache.commons.lang3.ArraySorter; 027import org.apache.commons.lang3.ArrayUtils; 028import org.apache.commons.lang3.ClassUtils; 029import org.apache.commons.lang3.reflect.FieldUtils; 030import org.apache.commons.lang3.tuple.Pair; 031 032/** 033 * Assists in implementing {@link Diffable#diff(Object)} methods. 034 * 035 * <p> 036 * All non-static, non-transient fields (including inherited fields) of the objects to diff are discovered using reflection and compared for differences. 037 * </p> 038 * 039 * <p> 040 * To use this class, write code as follows: 041 * </p> 042 * 043 * <pre>{@code 044 * public class Person implements Diffable<Person> { 045 * String name; 046 * int age; 047 * boolean smoker; 048 * ... 049 * 050 * public DiffResult<Person> diff(Person obj) { 051 * // No need for null check, as NullPointerException correct if obj is null 052 * return ReflectionDiffBuilder.<Person>builder() 053 * .setDiffBuilder(DiffBuilder.<Person>builder() 054 * .setLeft(this) 055 * .setRight(obj) 056 * .setStyle(ToStringStyle.SHORT_PREFIX_STYLE) 057 * .build()) 058 * .setExcludeFieldNames("userName", "password") 059 * .build() // -> ReflectionDiffBuilder 060 * .build(); // -> DiffResult 061 * } 062 * } 063 * }</pre> 064 * 065 * <p> 066 * The {@link ToStringStyle} passed to the constructor is embedded in the returned {@link DiffResult} and influences the style of the 067 * {@code DiffResult.toString()} method. This style choice can be overridden by calling {@link DiffResult#toString(ToStringStyle)}. 068 * </p> 069 * <p> 070 * See {@link DiffBuilder} for a non-reflection based version of this class. 071 * </p> 072 * 073 * @param <T> type of the left and right object to diff. 074 * @see Diffable 075 * @see Diff 076 * @see DiffResult 077 * @see ToStringStyle 078 * @see DiffBuilder 079 * @see AbstractBuilder#setForceAccessible(boolean) 080 * @since 3.6 081 */ 082public class ReflectionDiffBuilder<T> extends AbstractReflection implements Builder<DiffResult<T>> { 083 084 /** 085 * Constructs a new instance. 086 * 087 * @param <T> type of the left and right object. 088 * @since 3.15.0 089 */ 090 public static final class Builder<T> extends AbstractBuilder<Builder<T>> { 091 092 private String[] excludeFieldNames = ArrayUtils.EMPTY_STRING_ARRAY; 093 private DiffBuilder<T> diffBuilder; 094 095 /** 096 * Constructs a new instance. 097 */ 098 public Builder() { 099 // empty 100 } 101 102 /** 103 * Builds a new configured {@link ReflectionDiffBuilder}. 104 * 105 * @return A new configured {@link ReflectionDiffBuilder}. 106 */ 107 public ReflectionDiffBuilder<T> build() { 108 return new ReflectionDiffBuilder<>(this); 109 } 110 111 @Override 112 public ReflectionDiffBuilder<T> get() { 113 return build(); 114 } 115 116 /** 117 * Sets the DiffBuilder. 118 * 119 * @param diffBuilder The DiffBuilder. 120 * @return {@code this} instance. 121 */ 122 public Builder<T> setDiffBuilder(final DiffBuilder<T> diffBuilder) { 123 this.diffBuilder = diffBuilder; 124 return this; 125 } 126 127 /** 128 * Sets field names to exclude from output. Intended for fields like {@code "password"} or {@code "lastModificationDate"}. 129 * 130 * @param excludeFieldNames field names to exclude. 131 * @return {@code this} instance. 132 */ 133 public Builder<T> setExcludeFieldNames(final String... excludeFieldNames) { 134 this.excludeFieldNames = toExcludeFieldNames(excludeFieldNames); 135 return this; 136 } 137 138 } 139 140 /** 141 * A registry of objects to detect cyclical object references, avoid infinite loops, and stack overflows. 142 */ 143 private static final ThreadLocal<Set<Pair<IDKey, IDKey>>> REGISTRY = ThreadLocal.withInitial(HashSet::new); 144 145 /** 146 * Constructs a new {@link Builder}. 147 * 148 * @param <T> type of the left and right object. 149 * @return A new {@link Builder}. 150 * @since 3.15.0 151 */ 152 public static <T> Builder<T> builder() { 153 return new Builder<>(); 154 } 155 156 /** 157 * Gets the registry of object pairs being traversed by the reflection 158 * methods in the current thread. 159 * 160 * @return Set the registry of objects being traversed 161 */ 162 static Set<Pair<IDKey, IDKey>> getRegistry() { 163 return REGISTRY.get(); 164 } 165 166 /** 167 * Tests whether the registry contains the given object pair. 168 * <p> 169 * Used by the reflection methods to avoid infinite loops. 170 * Objects might be swapped therefore a check is needed if the object pair 171 * is registered in the given or swapped order. 172 * </p> 173 * 174 * @param lhs {@code this} object to lookup in registry 175 * @param rhs The other object to lookup on registry 176 * @return boolean {@code true} if the registry contains the given object. 177 */ 178 static boolean isRegistered(final Object lhs, final Object rhs) { 179 return isRegistered(lhs, rhs, getRegistry()); 180 } 181 182 /** 183 * Registers the given object pair. 184 * Used by the reflection methods to avoid infinite loops. 185 * 186 * @param lhs {@code this} object to register 187 * @param rhs the other object to register 188 */ 189 static void register(final Object lhs, final Object rhs) { 190 register(lhs, rhs, getRegistry()); 191 } 192 193 private static String[] toExcludeFieldNames(final String[] excludeFieldNames) { 194 if (excludeFieldNames == null) { 195 return ArrayUtils.EMPTY_STRING_ARRAY; 196 } 197 // clone and remove nulls 198 return ArraySorter.sort(ReflectionToStringBuilder.toNoNullStringArray(excludeFieldNames)); 199 } 200 201 /** 202 * Unregisters the given object pair. 203 * 204 * <p> 205 * Used by the reflection methods to avoid infinite loops. 206 * </p> 207 * 208 * @param lhs {@code this} object to unregister 209 * @param rhs the other object to unregister 210 */ 211 static void unregister(final Object lhs, final Object rhs) { 212 unregister(lhs, rhs, getRegistry(), REGISTRY); 213 } 214 215 private final DiffBuilder<T> diffBuilder; 216 217 /** 218 * Field names to exclude from output. Intended for fields like {@code "password"} or {@code "lastModificationDate"}. 219 */ 220 private String[] excludeFieldNames; 221 222 /** 223 * Constructs a new instance. 224 * 225 * @param builder A non-null Builder. 226 * @throws NullPointerException Thrown on null input. 227 */ 228 private ReflectionDiffBuilder(final Builder<T> builder) { 229 super(Objects.requireNonNull(builder, "builder")); 230 this.diffBuilder = Objects.requireNonNull(builder.diffBuilder, "diffBuilder"); 231 this.excludeFieldNames = Objects.requireNonNull(builder.excludeFieldNames, "excludeFieldNames"); 232 } 233 234 /** 235 * Constructs a new instance. 236 * 237 * @param diffBuilder A non-null DiffBuilder. 238 * @param excludeFieldNames A non-null String array. 239 * @throws NullPointerException Thrown on null input. 240 */ 241 private ReflectionDiffBuilder(final DiffBuilder<T> diffBuilder, final String[] excludeFieldNames) { 242 this(ReflectionDiffBuilder.<T>builder().setDiffBuilder(diffBuilder).setExcludeFieldNames(excludeFieldNames)); 243 } 244 245 /** 246 * Constructs a builder for the specified objects with the specified style. 247 * 248 * <p> 249 * If {@code left == right} or {@code left.equals(right)} then the builder will not evaluate any calls to {@code append(...)} and will return an empty 250 * {@link DiffResult} when {@link #build()} is executed. 251 * </p> 252 * 253 * @param left {@code this} object. 254 * @param right The object to diff against. 255 * @param style The style will use when outputting the objects, {@code null} uses the default 256 * @throws IllegalArgumentException Thrown if {@code left} or {@code right} is {@code null}. 257 * @deprecated Use {@link Builder}. 258 */ 259 @Deprecated 260 public ReflectionDiffBuilder(final T left, final T right, final ToStringStyle style) { 261 this(DiffBuilder.<T>builder().setLeft(left).setRight(right).setStyle(style).build(), ArrayUtils.EMPTY_STRING_ARRAY); 262 } 263 264 private boolean accept(final Field field) { 265 if (field.getName().indexOf(ClassUtils.INNER_CLASS_SEPARATOR_CHAR) != -1 || Modifier.isTransient(field.getModifiers()) 266 || Modifier.isStatic(field.getModifiers()) || Arrays.binarySearch(excludeFieldNames, field.getName()) >= 0) { 267 // Rejected. 268 return false; 269 } 270 return !field.isAnnotationPresent(DiffExclude.class); 271 } 272 273 /** 274 * Appends fields using reflection. 275 * 276 * @throws SecurityException Thrown if an underlying accessible object's method denies the request. 277 * @see SecurityManager#checkPermission 278 */ 279 private void appendFields(final Class<?> clazz) { 280 for (final Field field : FieldUtils.getAllFields(clazz)) { 281 if (accept(field)) { 282 try { 283 if (setAccessible(field)) { 284 diffBuilder.append(field.getName(), Reflection.getUnchecked(field, getLeft()), Reflection.getUnchecked(field, getRight())); 285 } 286 } catch (final RuntimeException e) { 287 // Ignored as per AccessibleObject / SecurityManager / InaccessibleObjectException 288 } 289 } 290 } 291 } 292 293 /** 294 * {@inheritDoc} 295 * 296 * @throws SecurityException Thrown if an underlying accessible object's method denies the request. 297 * @see SecurityManager#checkPermission 298 */ 299 @Override 300 public DiffResult<T> build() { 301 if (getLeft() == getRight() || isRegistered(getLeft(), getRight())) { 302 return diffBuilder.build(); 303 } 304 try { 305 register(getLeft(), getRight()); 306 appendFields(getLeft().getClass()); 307 return diffBuilder.build(); 308 } finally { 309 unregister(getLeft(), getRight()); 310 } 311 } 312 313 /** 314 * Gets the field names that should be excluded from the diff. 315 * 316 * @return The excludeFieldNames. 317 * @since 3.13.0 318 */ 319 public String[] getExcludeFieldNames() { 320 return excludeFieldNames.clone(); 321 } 322 323 private T getLeft() { 324 return diffBuilder.getLeft(); 325 } 326 327 private T getRight() { 328 return diffBuilder.getRight(); 329 } 330 331 /** 332 * Sets the field names to exclude. 333 * 334 * @param excludeFieldNames The field names to exclude from the diff or {@code null}. 335 * @return {@code this} instance. 336 * @since 3.13.0 337 * @deprecated Use {@link Builder#setExcludeFieldNames(String[])}. 338 */ 339 @Deprecated 340 public ReflectionDiffBuilder<T> setExcludeFieldNames(final String... excludeFieldNames) { 341 this.excludeFieldNames = toExcludeFieldNames(excludeFieldNames); 342 return this; 343 } 344 345}