View Javadoc
1   /*
2    * SPDX-License-Identifier: Apache-2.0
3    * See LICENSE file for details.
4    *
5    * Copyright 2012-2026 hazendaz
6    *
7    * Portions of initial baseline code (getter/setter test) by Rob Dawson (CodeBox)
8    */
9   package com.codebox.bean;
10  
11  import com.codebox.util.LombokBuilderUtil;
12  
13  import java.lang.reflect.Modifier;
14  
15  import net.bytebuddy.ByteBuddy;
16  import net.bytebuddy.NamingStrategy;
17  import net.bytebuddy.description.NamedElement;
18  import net.bytebuddy.description.modifier.Visibility;
19  import net.bytebuddy.description.type.TypeDescription;
20  import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
21  import net.bytebuddy.implementation.HashCodeMethod;
22  import net.bytebuddy.implementation.MethodDelegation;
23  import net.bytebuddy.implementation.ToStringMethod;
24  import net.bytebuddy.implementation.bind.annotation.Argument;
25  import net.bytebuddy.implementation.bind.annotation.This;
26  import net.bytebuddy.matcher.ElementMatchers;
27  
28  /**
29   * This helper class can be used to unit test the get/set/equals/canEqual/toString/hashCode methods of JavaBean-style
30   * Value Objects.
31   */
32  public enum JavaBeanTester {
33  
34      // Private Usage
35      ;
36  
37      /**
38       * Configure JavaBeanTester using Fluent API.
39       *
40       * @param <T>
41       *            the generic type
42       * @param clazz
43       *            the clazz
44       *
45       * @return A builder implementing the fluent API to configure JavaBeanTester
46       */
47      public static <T> JavaBeanTesterBuilder<T, ?> builder(final Class<T> clazz) {
48          // If class is final, use Object.class for comparison needs
49          if (Modifier.isFinal(clazz.getModifiers())) {
50              return new JavaBeanTesterBuilder<>(clazz, Object.class);
51          }
52  
53          // Lombok builder not supported by byte buddy, do not attempt to build extension class
54          if (LombokBuilderUtil.getLombokBuilderMethod(clazz) != null) {
55              return builder(clazz, null);
56          }
57  
58          // Build extension from class using byte buddy
59          Class<? extends T> loaded = new ByteBuddy().with(new NamingStrategy.AbstractBase() {
60              @Override
61              protected String name(TypeDescription superClass) {
62                  return clazz.getPackageName() + ".ByteBuddyExt" + superClass.getSimpleName();
63              }
64          }).subclass(clazz).method(ElementMatchers.isEquals())
65                  .intercept(MethodDelegation.to(InstanceOfEqualsInterceptor.class)).method(ElementMatchers.isHashCode())
66                  .intercept(HashCodeMethod.usingSuperClassOffset()).method(ElementMatchers.isToString())
67                  .intercept(ToStringMethod.prefixedBySimpleClassName()).method(ElementMatchers.named("canEqual"))
68                  .intercept(MethodDelegation.to(CanEqualInterceptor.class))
69                  .defineField("javabeanExtension", String.class, Visibility.PACKAGE_PRIVATE).make()
70                  .load(clazz.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER).getLoaded();
71  
72          // Builder with proper extension class
73          return builder(clazz, loaded);
74      }
75  
76      /**
77       * Configure JavaBeanTester using Fluent API.
78       *
79       * @param <T>
80       *            the generic type
81       * @param <E>
82       *            the element type
83       * @param clazz
84       *            the clazz
85       * @param extension
86       *            the extension
87       *
88       * @return A builder implementing the fluent API to configure JavaBeanTester
89       */
90      public static <T, E> JavaBeanTesterBuilder<T, E> builder(final Class<T> clazz, final Class<E> extension) {
91          return new JavaBeanTesterBuilder<>(clazz, extension);
92      }
93  
94      /**
95       * The Class CanEqualInterceptor.
96       */
97      public static final class CanEqualInterceptor {
98  
99          /**
100          * Prevents instantiation a new can equal interceptor.
101          */
102         CanEqualInterceptor() {
103             // Private constructor to prevent instantiation
104         }
105 
106         /**
107          * Can Equal Interceptor.
108          *
109          * @param object
110          *            The object to check can equals
111          * @return boolean of can equals
112          */
113         public static boolean canEqual(final Object object) {
114             return object instanceof NamedElement.WithRuntimeName;
115         }
116 
117     }
118 
119     /**
120      * The Class InstanceOfEqualsInterceptor.
121      */
122     public static final class InstanceOfEqualsInterceptor {
123 
124         /**
125          * Prevents instantiation a new instance of equals interceptor.
126          */
127         InstanceOfEqualsInterceptor() {
128             // Private constructor to prevent instantiation
129         }
130 
131         /**
132          * Equals.
133          *
134          * @param self
135          *            the self
136          * @param other
137          *            the other
138          * @return true, if successful
139          */
140         public static boolean equals(@This Object self, @Argument(0) Object other) {
141             if (self == other) {
142                 return true;
143             }
144             if ((other == null) || (!self.getClass().isInstance(other) && !other.getClass().isInstance(self))) {
145                 return false;
146             }
147             return self.hashCode() == other.hashCode();
148         }
149     }
150 
151 }