ValueClassConverter.java

package com.surrealdb;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

class ValueClassConverter<T> {

	private final Class<T> clazz;

	ValueClassConverter(Class<T> clazz) {
		this.clazz = clazz;
	}

	private static java.lang.Object convertSingleValue(final Value value) {
		if (value.isNull())
			return null;
		if (value.isBoolean())
			return value.getBoolean();
		if (value.isDouble())
			return value.getDouble();
		if (value.isLong())
			return value.getLong();
		if (value.isString())
			return value.getString();
		if (value.isRecordId())
			return value.getRecordId();
		if (value.isGeometry())
			return value.getGeometry();
		if (value.isBigDecimal())
			return value.getBigDecimal();
		if (value.isBytes())
			return value.getBytes();
		if (value.isUuid())
			return value.getUuid();
		if (value.isDuration())
			return value.getDuration();
		if (value.isDateTime())
			return value.getDateTime();
		throw new SurrealException("Unsupported value: " + value);
	}

	private static <T> void setSingleValue(final Field field, final Class<?> type, final T target, final Value value)
			throws IllegalAccessException {
		if (value.isNull()) {
			field.set(target, null);
		} else if (value.isBoolean()) {
			field.setBoolean(target, value.getBoolean());
		} else if (value.isDouble()) {
			final double d = value.getDouble();
			if (type == Double.TYPE)
				field.setDouble(target, d);
			else if (type == Float.TYPE)
				field.setFloat(target, (float) d);
			else if (type == Float.class)
				field.set(target, (float) d);
			else
				field.set(target, d);
		} else if (value.isLong()) {
			final long l = value.getLong();
			if (type == Long.TYPE)
				field.setLong(target, l);
			else if (type == Integer.TYPE)
				field.setInt(target, (int) l);
			else if (type == Integer.class)
				field.set(target, (int) l);
			else if (type == Short.TYPE)
				field.setShort(target, (short) l);
			else if (type == Short.class)
				field.set(target, (short) l);
			else
				field.set(target, l);
		} else if (value.isString()) {
			field.set(target, value.getString());
		} else if (value.isRecordId()) {
			if (field.getType() == Id.class) {
				field.set(target, value.getRecordId().getId());
			} else {
				field.set(target, value.getRecordId());
			}
		} else if (value.isGeometry()) {
			field.set(target, value.getGeometry());
		} else if (value.isBigDecimal()) {
			field.set(target, value.getBigDecimal());
		} else if (value.isBytes()) {
			field.set(target, value.getBytes());
		} else if (value.isUuid()) {
			field.set(target, value.getUuid());
		} else if (value.isDuration()) {
			field.set(target, value.getDuration());
		} else if (value.isDateTime()) {
			field.set(target, value.getDateTime());
		} else {
			throw new SurrealException("Unsupported value: " + value);
		}
	}

	private static java.lang.Object convertArrayValue(final Field field, final Value value)
			throws ReflectiveOperationException {
		if (value.isObject()) {
			final Class<?> subType = getGenericType(field, 0);
			if (subType == null) {
				throw new SurrealException("Unsupported field type: " + field);
			}
			return convert(subType, value.getObject());
		} else if (value.isArray()) {
			final List<java.lang.Object> arrayList = new ArrayList<>();
			for (final Value elementValue : value.getArray()) {
				arrayList.add(convertArrayValue(field, elementValue));
			}
			return arrayList;
		} else {
			return convertSingleValue(value);
		}
	}

	private static <T> T convert(Class<T> clazz, Object source) throws ReflectiveOperationException {
		final T target = clazz.getConstructor().newInstance();
		for (final Entry entry : source) {
			try {
				final String key = entry.getKey();
				final Value value = entry.getValue();
				final Field field = getInheritedDeclaredField(clazz, key);
				final Class<?> type = field.getType();

				field.setAccessible(true);

				if (Value.class.equals(type)) {
					field.set(target, value);
				} else if (value.isArray()) {
					final List<java.lang.Object> arrayList = new ArrayList<>();
					for (final Value elementValue : value.getArray()) {
						arrayList.add(convertArrayValue(field, elementValue));
					}
					setFieldObject(field, type, target, arrayList);
				} else if (value.isObject()) {
					if (Map.class.isAssignableFrom(type)) {
						final Map<String, java.lang.Object> map = new HashMap<>();
						final Class<?> subType = getGenericType(field, 1);
						if (subType == null) {
							throw new SurrealException("Unsupported field type: " + field);
						}
						for (final Entry mapEntry : value.getObject()) {
							final String entryKey = mapEntry.getKey();
							final Value entryValue = mapEntry.getValue();
							// todo - array support
							if (entryValue.isObject()) {
								map.put(entryKey, convert(subType, entryValue.getObject()));
							} else {
								map.put(entryKey, convertSingleValue(entryValue));
							}
						}
						setFieldObject(field, type, target, map);
					} else {
						java.lang.Object o = convert(type, value.getObject());
						setFieldObject(field, type, target, o);
					}
				} else {
					setFieldSingleValue(field, type, target, value);
				}
			} catch (NoSuchFieldException e) {
				// Safe to ignore
			}
		}
		return target;
	}

	private static <T, V> void setFieldObject(Field field, Class<?> type, T target, V value)
			throws ReflectiveOperationException {
		if (Optional.class.equals(type)) {
			field.set(target, Optional.of(value));
		} else if (type == byte[].class && value instanceof List) {
			// Special handling for byte[] fields
			final List<?> list = (List<?>) value;
			final byte[] bytes = new byte[list.size()];
			for (int i = 0; i < list.size(); i++) {
				final java.lang.Object element = list.get(i);
				if (element instanceof Number) {
					bytes[i] = ((Number) element).byteValue();
				} else {
					throw new SurrealException("Cannot convert " + element.getClass() + " to byte");
				}
			}
			field.set(target, bytes);
		} else {
			field.set(target, value);
		}
	}

	private static <T, V> void setFieldSingleValue(Field field, Class<?> type, T target, Value value)
			throws ReflectiveOperationException {
		if (Optional.class.equals(type)) {
			final java.lang.Object converted = convertSingleValue(value);
			if (converted == null) {
				field.set(target, Optional.empty());
			} else {
				field.set(target, Optional.of(converted));
			}
		} else {
			setSingleValue(field, type, target, value);
		}
	}

	static Class<?> getGenericType(final Field field, final int index) {
		// Check if the field is parameterized
		if (field.getGenericType() instanceof ParameterizedType) {
			ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();

			// Get the actual type arguments (generics)
			final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();

			if (actualTypeArguments.length > index) {
				// Return the first type argument
				return (Class<?>) actualTypeArguments[index];
			}
		}
		return null;
	}

	static Field getInheritedDeclaredField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
		while (clazz != null) {
			try {
				return clazz.getDeclaredField(fieldName);
			} catch (NoSuchFieldException e) {
				clazz = clazz.getSuperclass();
			}
		}
		throw new NoSuchFieldException("Field '" + fieldName + "' not found in class hierarchy.");
	}

	final T convert(final Value value) {
		try {
			if (value.isNone() || value.isNull())
				return null;

			if (!value.isObject())
				throw new SurrealException("Unexpected value: " + value);

			return convert(clazz, value.getObject());
		} catch (ReflectiveOperationException e) {
			throw new SurrealException("Failed to create instance of " + clazz.getName(), e);
		}
	}
}