ByteBuddyBeanCopier.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.Method;
import java.util.HashMap;
import java.util.Map;
/**
* The Class ByteBuddyBeanCopier.
*/
public final class ByteBuddyBeanCopier {
/**
* Instantiates a new byte buddy bean copier.
*/
private ByteBuddyBeanCopier() {
throw new AssertionError("Utility class should not be instantiated");
}
/**
* Copy.
*
* @param source
* the source
* @param target
* the target
* @param converter
* the converter
*/
public static void copy(Object source, Object target, BooleanConverter converter) {
if (source == null || target == null) {
return;
}
Class<?> clazz = source.getClass();
Map<String, Method> getters = new HashMap<>();
Map<String, Method> setters = new HashMap<>();
for (Method method : clazz.getMethods()) {
if (ByteBuddyBeanCopier.isGetter(method)) {
String property = ByteBuddyBeanCopier.decapitalize(ByteBuddyBeanCopier.extractPropertyName(method));
getters.put(property, method);
} else if (ByteBuddyBeanCopier.isSetter(method)) {
String property = ByteBuddyBeanCopier.decapitalize(method.getName().substring(3));
setters.put(property, method);
}
}
for (Map.Entry<String, Method> entry : setters.entrySet()) {
String property = entry.getKey();
Method setter = entry.getValue();
Method getter = getters.get(property);
try {
Object value = null;
if (getter != null) {
value = getter.invoke(source);
} else if (isBooleanType(setter.getParameterTypes()[0])) {
// Try 'is' method for Boolean if getter is missing
String isMethodName = "is" + ByteBuddyBeanCopier.capitalize(property);
try {
Method isMethod = clazz.getMethod(isMethodName);
if (isBooleanType(isMethod.getReturnType())) {
value = isMethod.invoke(source);
}
} catch (NoSuchMethodException ignored) {
// No 'is' method, skip
}
}
if (converter != null && isBooleanType(setter.getParameterTypes()[0])) {
value = converter.convert(value, setter.getParameterTypes()[0]);
}
if (value != null || !setter.getParameterTypes()[0].isPrimitive()) {
setter.invoke(target, value);
}
} catch (Exception e) {
throw new RuntimeException("Failed to copy property: " + property, e);
}
}
}
/**
* Checks if is getter.
*
* @param method
* the method
* @return true, if is getter
*/
private static boolean isGetter(Method method) {
if (method.getParameterCount() != 0) {
return false;
}
String name = method.getName();
return name.startsWith("get") && !method.getReturnType().equals(void.class) && name.length() > 3
|| name.startsWith("is") && isBooleanType(method.getReturnType()) && name.length() > 2;
}
/**
* Checks if is setter.
*
* @param method
* the method
* @return true, if is setter
*/
private static boolean isSetter(Method method) {
return method.getName().startsWith("set") && method.getParameterCount() == 1
&& method.getReturnType().equals(void.class);
}
/**
* Checks if is boolean type.
*
* @param type
* the type
* @return true, if is boolean type
*/
private static boolean isBooleanType(Class<?> type) {
return type == boolean.class || type == Boolean.class;
}
/**
* Extract property name.
*
* @param getter
* the getter
* @return the string
*/
private static String extractPropertyName(Method getter) {
String name = getter.getName();
if (name.startsWith("get")) {
return name.substring(3);
}
if (name.startsWith("is")) {
return name.substring(2);
}
throw new IllegalArgumentException("Not a getter method: " + name);
}
/**
* Decapitalize.
*
* @param name
* the name
* @return the string
*/
private static String decapitalize(String name) {
if (name == null || name.isEmpty()) {
return name;
}
if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0))) {
return name;
}
return Character.toLowerCase(name.charAt(0)) + name.substring(1);
}
/**
* Capitalize.
*
* @param name
* the name
* @return the string
*/
private static String capitalize(String name) {
if (name == null || name.isEmpty()) {
return name;
}
return Character.toUpperCase(name.charAt(0)) + name.substring(1);
}
/**
* The Interface BooleanConverter.
*/
public interface BooleanConverter {
/**
* Convert.
*
* @param value
* the value
* @param targetType
* the target type
* @return the object
*/
Object convert(Object value, Class<?> targetType);
}
}