ValueBuilder.java
/*
* JavaBean Tester (https://github.com/hazendaz/javabean-tester)
*
* Copyright 2012-2024 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 com.codebox.enums.LoadData;
import com.codebox.enums.LoadType;
import com.codebox.instance.ClassInstance;
import com.codebox.instance.ConstructorInstance;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Value Builder Builds values from given type.
*/
@Data
public class ValueBuilder {
/** The Constant Logger. */
private static final Logger logger = LoggerFactory.getLogger(ValueBuilder.class);
/** The load data. */
private LoadData loadData;
/**
* Builds the value.
*
* @param <T>
* the generic type
* @param clazz
* the clazz
* @param loadType
* the load type
*
* @return the object
*/
public <T> Object buildValue(final Class<T> clazz, final LoadType loadType) {
// Next check for a no-arg constructor
final Constructor<?>[] ctrs = clazz.getConstructors();
for (final Constructor<?> ctr : ctrs) {
if (ctr.getParameterTypes().length == 0 && clazz != String.class) {
if (this.loadData == LoadData.ON && !containsSelf(clazz)) {
// Load Underlying Data to deeply test when it doesn't contain self
final JavaBeanTesterWorker<T, Object> beanTesterWorker = new JavaBeanTesterWorker<>(clazz);
beanTesterWorker.setLoadData(this.loadData);
beanTesterWorker.getterSetterTests(new ClassInstance<T>().newInstance(clazz));
return null;
}
// The class has a no-arg constructor, so just call it
return ConstructorInstance.newInstance(ctr);
}
}
// Specific rules for common classes
Object returnObject = null;
switch (loadType) {
case ALTERNATE_DATA:
returnObject = ValueBuilder.setAlternateValues(clazz);
break;
case NULL_DATA:
returnObject = ValueBuilder.setNullValues(clazz);
break;
case STANDARD_DATA:
default:
returnObject = ValueBuilder.setStandardValues(clazz);
break;
}
if (returnObject != null || loadType == LoadType.NULL_DATA) {
return returnObject;
}
if (clazz.isAssignableFrom(BigDecimal.class)) {
return BigDecimal.ONE;
}
if (clazz.isAssignableFrom(UUID.class)) {
return UUID.fromString("00000000-0000-0000-0000-000123456789");
}
if (clazz.isAssignableFrom(Instant.class)) {
return Instant.ofEpochSecond(1L);
}
if (clazz.isAssignableFrom(List.class)) {
return new ArrayList<>();
}
if (clazz.isAssignableFrom(Map.class)) {
return new HashMap<>();
}
if (clazz.isAssignableFrom(ConcurrentMap.class)) {
return new ConcurrentHashMap<>();
}
if (clazz.isAssignableFrom(Set.class)) {
return new TreeSet<>();
}
if (clazz.isAssignableFrom(Date.class)) {
return new Date();
}
if (clazz.isAssignableFrom(LocalDate.class)) {
return LocalDate.now();
}
if (clazz.isAssignableFrom(LocalDateTime.class)) {
return LocalDateTime.of(2000, 10, 1, 0, 0);
}
if (clazz.isAssignableFrom(LocalTime.class)) {
return LocalTime.of(0, 0);
}
if (clazz.isAssignableFrom(OffsetDateTime.class)) {
return OffsetDateTime.of(2000, 10, 1, 0, 0, 0, 0, ZoneOffset.MIN);
}
if (clazz.isAssignableFrom(ZonedDateTime.class)) {
return ZonedDateTime.of(LocalDateTime.of(2020, 11, 16, 10, 26, 00, 01), ZoneId.of("UTC"));
}
if (clazz == Logger.class) {
return LoggerFactory.getLogger(clazz);
}
if (clazz.isEnum()) {
return clazz.getEnumConstants()[0];
}
// XXX Add additional rules here
// XXX Don't fail this...until alternative solution is determined
ValueBuilder.logger.warn(
"Unable to build value for class '{}', please raise ticket with JavaBeanTester for desired support.",
clazz.getName());
return null;
}
/**
* Set using alternate test values.
*
* @param <T>
* the type parameter associated with the class under test.
* @param clazz
* the class under test.
*
* @return Object the Object to use for test.
*/
private static <T> Object setAlternateValues(final Class<T> clazz) {
return ValueBuilder.setValues(clazz, "ALT_VALUE", 1, Boolean.FALSE, Integer.valueOf(2), Long.valueOf(2),
Double.valueOf(2.0), Float.valueOf(2.0F), Character.valueOf('N'), Byte.valueOf((byte) 2));
}
/**
* Set using null test values.
*
* @param <T>
* the type parameter associated with the class under test.
* @param clazz
* the class under test.
*
* @return Object the Object to use for test.
*/
private static <T> Object setNullValues(final Class<T> clazz) {
return ValueBuilder.setValues(clazz, null, 0, null, null, null, null, null, null, null);
}
/**
* Set using standard test values.
*
* @param <T>
* the type parameter associated with the class under test.
* @param clazz
* the class under test.
*
* @return Object the Object to use for test.
*/
private static <T> Object setStandardValues(final Class<T> clazz) {
return ValueBuilder.setValues(clazz, "TEST_VALUE", 1, Boolean.TRUE, Integer.valueOf(1), Long.valueOf(1),
Double.valueOf(1.0), Float.valueOf(1.0F), Character.valueOf('Y'), Byte.valueOf((byte) 1));
}
/**
* Set Values for object.
*
* @param <T>
* the type parameter associated with the class under test.
* @param clazz
* the class instance under test.
* @param string
* value of string object.
* @param arrayLength
* amount of array objects to create.
* @param booleanValue
* value of boolean object.
* @param integerValue
* value of integer object.
* @param longValue
* value of long object.
* @param doubleValue
* value of double object.
* @param floatValue
* value of float object.
* @param characterValue
* value of character object.
* @param byteValue
* value of character object.
*
* @return Object value determined by input class. If not found, returns null.
*/
private static <T> Object setValues(final Class<T> clazz, final String string, final int arrayLength,
final Boolean booleanValue, final Integer integerValue, final Long longValue, final Double doubleValue,
final Float floatValue, final Character characterValue, final Byte byteValue) {
if (clazz == String.class) {
return string;
}
if (clazz.isArray()) {
return Array.newInstance(clazz.getComponentType(), arrayLength);
}
if (clazz == boolean.class || clazz == Boolean.class) {
return ValueBuilder.initializeBoolean(clazz, booleanValue);
}
if (clazz == int.class || clazz == Integer.class) {
return ValueBuilder.initializeInteger(clazz, integerValue);
}
if (clazz == long.class || clazz == Long.class) {
return ValueBuilder.initializeLong(clazz, longValue);
}
if (clazz == double.class || clazz == Double.class) {
return ValueBuilder.initializeDouble(clazz, doubleValue);
}
if (clazz == float.class || clazz == Float.class) {
return ValueBuilder.initializeFloat(clazz, floatValue);
}
if (clazz == char.class || clazz == Character.class) {
return ValueBuilder.initializeCharacter(clazz, characterValue);
}
if (clazz == byte.class || clazz == Byte.class) {
return ValueBuilder.initializeByte(clazz, byteValue);
}
return null;
}
/**
* Initialize boolean.
*
* @param <T>
* the generic type
* @param clazz
* the clazz
* @param booleanValue
* the boolean value
*
* @return the object
*/
private static <T> Object initializeBoolean(final Class<T> clazz, final Boolean booleanValue) {
if (clazz == boolean.class && booleanValue == null) {
return Boolean.FALSE;
}
return booleanValue;
}
/**
* Initialize integer.
*
* @param <T>
* the generic type
* @param clazz
* the clazz
* @param integerValue
* the integer value
*
* @return the object
*/
private static <T> Object initializeInteger(final Class<T> clazz, final Integer integerValue) {
if (clazz == int.class && integerValue == null) {
return Integer.valueOf(-1);
}
return integerValue;
}
/**
* Initialize long.
*
* @param <T>
* the generic type
* @param clazz
* the clazz
* @param longValue
* the long value
*
* @return the object
*/
private static <T> Object initializeLong(final Class<T> clazz, final Long longValue) {
if (clazz == long.class && longValue == null) {
return Long.valueOf(-1);
}
return longValue;
}
/**
* Initialize double.
*
* @param <T>
* the generic type
* @param clazz
* the clazz
* @param doubleValue
* the double value
*
* @return the object
*/
private static <T> Object initializeDouble(final Class<T> clazz, final Double doubleValue) {
if (clazz == double.class && doubleValue == null) {
return Double.valueOf(-1.0);
}
return doubleValue;
}
/**
* Initialize float.
*
* @param <T>
* the generic type
* @param clazz
* the clazz
* @param floatValue
* the float value
*
* @return the object
*/
private static <T> Object initializeFloat(final Class<T> clazz, final Float floatValue) {
if (clazz == float.class && floatValue == null) {
return Float.valueOf(-1.0F);
}
return floatValue;
}
/**
* Initialize character.
*
* @param <T>
* the generic type
* @param clazz
* the clazz
* @param characterValue
* the character value
*
* @return the object
*/
private static <T> Object initializeCharacter(final Class<T> clazz, final Character characterValue) {
if (clazz == char.class && characterValue == null) {
return Character.valueOf('\u0000');
}
return characterValue;
}
/**
* Initialize byte.
*
* @param <T>
* the generic type
* @param clazz
* the clazz
* @param byteValue
* the byte value
*
* @return the object
*/
private static <T> Object initializeByte(final Class<T> clazz, final Byte byteValue) {
if (clazz == byte.class && byteValue == null) {
return Byte.valueOf((byte) -1);
}
return byteValue;
}
/**
* Contains self.
*
* @param <T>
* the generic type
* @param clazz
* the clazz
* @return true, if successful
*/
private <T> boolean containsSelf(final Class<T> clazz) {
final List<Field> fields = Arrays.asList(clazz.getDeclaredFields());
for (Field field : fields) {
if (field.getType().equals(clazz)) {
return true;
}
}
return false;
}
}