JavaBeanTester.java
/*
* JavaBean Tester (https://github.com/hazendaz/javabean-tester)
*
* Copyright 2012-2025 Hazendaz.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of The Apache Software License,
* Version 2.0 which accompanies this distribution, and is available at
* http://www.apache.org/licenses/LICENSE-2.0.txt
*
* Contributors:
* CodeBox (Rob Dawson).
* Hazendaz (Jeremy Landis).
*/
package com.codebox.bean;
import java.lang.reflect.Modifier;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.NamingStrategy;
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.HashCodeMethod;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.ToStringMethod;
import net.bytebuddy.implementation.bind.annotation.Argument;
import net.bytebuddy.implementation.bind.annotation.This;
import net.bytebuddy.matcher.ElementMatchers;
/**
* This helper class can be used to unit test the get/set/equals/canEqual/toString/hashCode methods of JavaBean-style
* Value Objects.
*/
public enum JavaBeanTester {
// Private Usage
;
/**
* Configure JavaBeanTester using Fluent API.
*
* @param <T>
* the generic type
* @param clazz
* the clazz
*
* @return A builder implementing the fluent API to configure JavaBeanTester
*/
public static <T> JavaBeanTesterBuilder<T, ?> builder(final Class<T> clazz) {
// If class is final, use Object.class for comparison needs
if (Modifier.isFinal(clazz.getModifiers())) {
return new JavaBeanTesterBuilder<>(clazz, Object.class);
}
// Build extension from class using byte buddy
Class<? extends T> loaded = new ByteBuddy().with(new NamingStrategy.AbstractBase() {
@Override
protected String name(TypeDescription superClass) {
return clazz.getPackageName() + ".ByteBuddyExt" + superClass.getSimpleName();
}
}).subclass(clazz).method(ElementMatchers.isEquals())
.intercept(MethodDelegation.to(InstanceOfEqualsInterceptor.class)).method(ElementMatchers.isHashCode())
.intercept(HashCodeMethod.usingSuperClassOffset()).method(ElementMatchers.isToString())
.intercept(ToStringMethod.prefixedBySimpleClassName()).method(ElementMatchers.named("canEqual"))
.intercept(MethodDelegation.to(CanEqualInterceptor.class))
.defineField("javabeanExtension", String.class, Visibility.PACKAGE_PRIVATE).make()
.load(clazz.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER).getLoaded();
// Builder with proper extension class
return builder(clazz, loaded);
}
/**
* Configure JavaBeanTester using Fluent API.
*
* @param <T>
* the generic type
* @param <E>
* the element type
* @param clazz
* the clazz
* @param extension
* the extension
*
* @return A builder implementing the fluent API to configure JavaBeanTester
*/
public static <T, E> JavaBeanTesterBuilder<T, E> builder(final Class<T> clazz, final Class<E> extension) {
return new JavaBeanTesterBuilder<>(clazz, extension);
}
/**
* The Class CanEqualInterceptor.
*/
public final static class CanEqualInterceptor {
/**
* Prevents instantiation a new can equal interceptor.
*/
CanEqualInterceptor() {
// Private constructor to prevent instantiation
}
/**
* Can Equal Interceptor.
*
* @param object
* The object to check can equals
* @return boolean of can equals
*/
public static boolean canEqual(final Object object) {
return object instanceof NamedElement.WithRuntimeName;
}
}
/**
* The Class InstanceOfEqualsInterceptor.
*/
public final static class InstanceOfEqualsInterceptor {
/**
* Prevents instantiation a new instance of equals interceptor.
*/
InstanceOfEqualsInterceptor() {
// Private constructor to prevent instantiation
}
/**
* Equals.
*
* @param self
* the self
* @param other
* the other
* @return true, if successful
*/
public static boolean equals(@This Object self, @Argument(0) Object other) {
if (self == other) {
return true;
}
if ((other == null) || (!self.getClass().isInstance(other) && !other.getClass().isInstance(self))) {
return false;
}
return self.hashCode() == other.hashCode();
}
}
}