ObjectReader.java

package com.alibaba.fastjson2.reader;

import com.alibaba.fastjson2.*;
import com.alibaba.fastjson2.util.Fnv;

import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;
import java.util.function.Function;

/**
 * ObjectReader is responsible for deserializing JSON data into Java objects.
 * It provides a set of methods for creating object instances, reading JSON data,
 * and handling field mapping.
 *
 * <p>This interface supports various features including:
 * <ul>
 *   <li>Object creation with different parameter types</li>
 *   <li>JSON object and array reading</li>
 *   <li>Field mapping and value setting</li>
 *   <li>Auto-type support for polymorphic deserialization</li>
 *   <li>Custom feature configuration</li>
 * </ul>
 *
 * <p>Example usage:
 * <pre>
 * // Get an ObjectReader for a specific type
 * ObjectReader&lt;User&gt; reader = JSONFactory.getDefaultObjectReaderProvider().getObjectReader(User.class);
 *
 * // Read from JSON string
 * String jsonString = "{\"id\":1,\"name\":\"John\"}";
 * User user = reader.readObject(JSONReader.of(jsonString));
 *
 * // Create instance with map data
 * Map&lt;String, Object&gt; data = new HashMap&lt;&gt;();
 * data.put("id", 1);
 * data.put("name", "John");
 * User user2 = reader.createInstance(data);
 * </pre>
 *
 * @param <T> the type of objects that this ObjectReader can deserialize
 * @since 2.0.0
 */
@SuppressWarnings({"rawtypes", "unchecked"})
public interface ObjectReader<T> {
    long HASH_TYPE = Fnv.hashCode64("@type");
    String VALUE_NAME = "@value";

    /**
     * @return {@link T}
     * @throws UnsupportedOperationException If the method is not overloaded or otherwise
     */
    default T createInstance() {
        return createInstance(0);
    }

    /**
     * @return {@link T}
     * @throws UnsupportedOperationException If the method is not overloaded or otherwise
     */
    default T createInstance(long features) {
        throw new UnsupportedOperationException();
    }

    /**
     * @return {@link T}
     * @throws UnsupportedOperationException If the method is not overloaded or otherwise
     */
    default T createInstance(Collection collection) {
        return createInstance(collection, 0L);
    }

    /**
     * @return {@link T}
     * @throws UnsupportedOperationException If the method is not overloaded or otherwise
     */
    default T createInstance(Collection collection, JSONReader.Feature... features) {
        return createInstance(collection, JSONReader.Feature.of(features));
    }

    /**
     * @return {@link T}
     * @throws UnsupportedOperationException If the method is not overloaded or otherwise
     */
    default T createInstance(Collection collection, long features) {
        throw new UnsupportedOperationException(this.getClass().getName());
    }

    /**
     * Accepts extra field data that is not mapped to any specific field in the object.
     * This method is called when deserializing JSON objects that contain fields not
     * present in the target class.
     *
     * @param object the object being deserialized
     * @param fieldName the name of the extra field
     * @param fieldValue the value of the extra field
     */
    default void acceptExtra(Object object, String fieldName, Object fieldValue) {
        acceptExtra(object, fieldName, fieldValue, this.getFeatures());
    }

    /**
     * Accepts extra field data that is not mapped to any specific field in the object.
     * This method is called when deserializing JSON objects that contain fields not
     * present in the target class.
     *
     * @param object the object being deserialized
     * @param fieldName the name of the extra field
     * @param fieldValue the value of the extra field
     * @param features the JSON reader features to use
     */
    default void acceptExtra(Object object, String fieldName, Object fieldValue, long features) {
    }

    /**
     * Creates an instance of the object type from a map of field values using the specified features.
     *
     * @param map the map containing field names and values
     * @param features the JSON reader features to use
     * @return a new instance of the object populated with values from the map
     */
    default T createInstance(Map map, JSONReader.Feature... features) {
        long featuresValue = 0;
        for (int i = 0; i < features.length; i++) {
            featuresValue |= features[i].mask;
        }
        return createInstance(map, featuresValue);
    }

    /**
     * @return {@link T}
     * @throws JSONException If a suitable ObjectReader is not found
     */
    default T createInstance(Map map, long features) {
        ObjectReaderProvider provider = JSONFactory.getDefaultObjectReaderProvider();
        Object typeKey = map.get(getTypeKey());

        if (typeKey instanceof String) {
            String typeName = (String) typeKey;
            long typeHash = Fnv.hashCode64(typeName);
            ObjectReader<T> reader = null;
            if ((features & JSONReader.Feature.SupportAutoType.mask) != 0 || this instanceof ObjectReaderSeeAlso) {
                reader = autoType(provider, typeHash);
            }

            if (reader == null) {
                reader = provider.getObjectReader(
                        typeName, getObjectClass(), features | getFeatures()
                );
            }

            if (reader != this && reader != null) {
                return reader.createInstance(map, features);
            }
        }

        T object = createInstance(0L);
        return accept(object, map, features);
    }

    /**
     * Accepts field values from a map and populates the specified object instance.
     *
     * @param object the object instance to populate
     * @param map the map containing field names and values
     * @param features the JSON reader features to use
     * @return the populated object instance
     */
    default T accept(T object, Map map, long features) {
        for (Map.Entry entry : (Iterable<Map.Entry>) map.entrySet()) {
            String entryKey = entry.getKey().toString();
            Object fieldValue = entry.getValue();

            FieldReader fieldReader = getFieldReader(entryKey);
            if (fieldReader == null) {
                acceptExtra(object, entryKey, entry.getValue(), features);
                continue;
            }

            fieldReader.acceptAny(object, fieldValue, features);
        }

        Function buildFunction = getBuildFunction();
        return buildFunction != null
                ? (T) buildFunction.apply(object)
                : object;
    }

    /**
     * Creates an instance of the object type using a non-default constructor with the specified values.
     *
     * @param values the map of field hash codes to values
     * @return a new instance of the object created with a non-default constructor
     * @throws UnsupportedOperationException if the method is not overloaded or otherwise
     */
    default T createInstanceNoneDefaultConstructor(Map<Long, Object> values) {
        throw new UnsupportedOperationException();
    }

    /**
     * Gets the features enabled by this ObjectReader.
     *
     * @return the enabled features as a bit mask
     */
    default long getFeatures() {
        return 0L;
    }

    /**
     * Gets the type key used for auto-type support. This key is used to identify
     * the type information in JSON objects.
     *
     * @return the type key string
     */
    default String getTypeKey() {
        return "@type";
    }

    /**
     * Gets the hash code of the type key used for auto-type support.
     *
     * @return the hash code of the type key
     */
    default long getTypeKeyHash() {
        return HASH_TYPE;
    }

    /**
     * Gets the class of objects that this ObjectReader can deserialize.
     *
     * @return the object class, or null if not specified
     */
    default Class<T> getObjectClass() {
        return null;
    }

    /**
     * Gets the FieldReader for the specified field hash code.
     *
     * @param hashCode the hash code of the field name
     * @return the FieldReader for the field, or null if not found
     */
    default FieldReader getFieldReader(long hashCode) {
        return null;
    }

    /**
     * Gets the FieldReader for the specified field hash code, using lowercase matching.
     *
     * @param hashCode the hash code of the field name (lowercase)
     * @return the FieldReader for the field, or null if not found
     */
    default FieldReader getFieldReaderLCase(long hashCode) {
        return null;
    }

    /**
     * Sets the value of a field in the specified object.
     *
     * @param object the object in which to set the field value
     * @param fieldName the name of the field to set
     * @param fieldNameHashCode the hash code of the field name
     * @param value the integer value to set
     * @return true if the field was successfully set, false otherwise
     */
    default boolean setFieldValue(Object object, String fieldName, long fieldNameHashCode, int value) {
        FieldReader fieldReader = getFieldReader(fieldNameHashCode);
        if (fieldReader == null) {
            return false;
        }
        fieldReader.accept(object, value);
        return true;
    }

    /**
     * Sets the value of a field in the specified object.
     *
     * @param object the object in which to set the field value
     * @param fieldName the name of the field to set
     * @param fieldNameHashCode the hash code of the field name
     * @param value the long value to set
     * @return true if the field was successfully set, false otherwise
     */
    default boolean setFieldValue(Object object, String fieldName, long fieldNameHashCode, long value) {
        FieldReader fieldReader = getFieldReader(fieldNameHashCode);
        if (fieldReader == null) {
            return false;
        }
        fieldReader.accept(object, value);
        return true;
    }

    /**
     * Gets the FieldReader for the specified field name.
     *
     * @param fieldName the name of the field
     * @return the FieldReader for the field, or null if not found
     */
    default FieldReader getFieldReader(String fieldName) {
        long fieldNameHash = Fnv.hashCode64(fieldName);
        FieldReader fieldReader = getFieldReader(fieldNameHash);

        if (fieldReader == null) {
            fieldReader = getFieldReaderLCase(fieldNameHash);
            if (fieldReader == null) {
                long fieldNameHashLCase = Fnv.hashCode64LCase(fieldName);
                if (fieldNameHashLCase != fieldNameHash) {
                    fieldReader = getFieldReaderLCase(fieldNameHashLCase);
                }
            }
        }

        return fieldReader;
    }

    /**
     * Sets the value of a field in the specified object.
     *
     * @param object the object in which to set the field value
     * @param fieldName the name of the field to set
     * @param value the object value to set
     * @return true if the field was successfully set, false otherwise
     */
    default boolean setFieldValue(Object object, String fieldName, Object value) {
        FieldReader fieldReader = getFieldReader(fieldName);
        if (fieldReader == null) {
            return false;
        }
        fieldReader.accept(object, value);
        return true;
    }

    /**
     * Gets the build function used to construct the final object instance.
     *
     * @return the build function, or null if not specified
     */
    default Function getBuildFunction() {
        return null;
    }

    /**
     * Resolves an ObjectReader for the specified type hash using the JSON reader context.
     *
     * @param context the JSON reader context
     * @param typeHash the hash code of the type name
     * @return the ObjectReader for the type, or null if not found
     */
    default ObjectReader autoType(JSONReader.Context context, long typeHash) {
        return context.getObjectReaderAutoType(typeHash);
    }

    /**
     * Resolves an ObjectReader for the specified type hash using the ObjectReaderProvider.
     *
     * @param provider the ObjectReaderProvider
     * @param typeHash the hash code of the type name
     * @return the ObjectReader for the type, or null if not found
     */
    default ObjectReader autoType(ObjectReaderProvider provider, long typeHash) {
        return provider.getObjectReader(typeHash);
    }

    /**
     * Reads an object from JSONB format.
     *
     * @param jsonReader the JSONReader to use for parsing
     * @param fieldType the type of the field being read
     * @param fieldName the name of the field being read
     * @param features the features to use
     * @return the deserialized object
     * @throws JSONException if a suitable ObjectReader is not found
     */
    default T readJSONBObject(JSONReader jsonReader, Type fieldType, Object fieldName, long features) {
        if (jsonReader.isArray() &&
                jsonReader.isSupportBeanArray()) {
            return readArrayMappingJSONBObject(jsonReader, fieldType, fieldName, features);
        }

        T object = null;
        jsonReader.nextIfObjectStart();

        JSONReader.Context context = jsonReader.getContext();
        long features2 = context.getFeatures() | features;
        for (int i = 0; ; ++i) {
            if (jsonReader.nextIfObjectEnd()) {
                break;
            }
            long hash = jsonReader.readFieldNameHashCode();

            if (hash == getTypeKeyHash() && i == 0) {
                long typeHash = jsonReader.readTypeHashCode();
                ObjectReader reader = autoType(context, typeHash);

                if (reader == null) {
                    String typeName = jsonReader.getString();
                    reader = context.getObjectReaderAutoType(typeName, null);

                    if (reader == null) {
                        throw new JSONException(jsonReader.info("No suitable ObjectReader found for " + typeName));
                    }
                }

                if (reader == this) {
                    continue;
                }

                return (T) reader.readJSONBObject(jsonReader, fieldType, fieldName, features);
            }

            if (hash == 0) {
                continue;
            }

            FieldReader fieldReader = getFieldReader(hash);
            if (fieldReader == null && jsonReader.isSupportSmartMatch(features2 | getFeatures())) {
                long nameHashCodeLCase = jsonReader.getNameHashCodeLCase();
                if (nameHashCodeLCase != hash) {
                    fieldReader = getFieldReaderLCase(nameHashCodeLCase);
                }
            }

            if (fieldReader == null) {
                jsonReader.skipValue();
                continue;
            }

            if (object == null) {
                object = createInstance(features2);
            }

            fieldReader.readFieldValue(jsonReader, object);
        }

        return object != null
                ? object
                : createInstance(features2);
    }

    /**
     * Reads an object from JSONB format with array mapping.
     *
     * @param jsonReader the JSONReader to use for parsing
     * @param fieldType the type of the field being read
     * @param fieldName the name of the field being read
     * @param features the features to use
     * @return the deserialized object
     * @throws UnsupportedOperationException if the method is not overloaded or otherwise
     */
    default T readArrayMappingJSONBObject(JSONReader jsonReader, Type fieldType, Object fieldName, long features) {
        throw new UnsupportedOperationException();
    }

    /**
     * Reads an object from JSON format with array mapping.
     *
     * @param jsonReader the JSONReader to use for parsing
     * @param fieldType the type of the field being read
     * @param fieldName the name of the field being read
     * @param features the features to use
     * @return the deserialized object
     * @throws UnsupportedOperationException if the method is not overloaded or otherwise
     */
    default T readArrayMappingObject(JSONReader jsonReader, Type fieldType, Object fieldName, long features) {
        throw new UnsupportedOperationException();
    }

    /**
     * Reads an object from the specified JSON string using the provided features.
     *
     * @param str the JSON string to parse
     * @param features the JSON reader features to use
     * @return the deserialized object
     */
    default T readObject(String str, JSONReader.Feature... features) {
        try (JSONReader jsonReader = JSONReader.of(str, JSONFactory.createReadContext(features))) {
            return readObject(jsonReader, null, null, getFeatures());
        }
    }

    /**
     * Reads an object from the specified JSONReader.
     *
     * @param jsonReader the JSONReader to use for parsing
     * @return the deserialized object
     */
    default T readObject(JSONReader jsonReader) {
        return readObject(jsonReader, null, null, getFeatures());
    }

    /**
     * Reads an object from the specified JSONReader using the provided features.
     *
     * @param jsonReader the JSONReader to use for parsing
     * @param features the features to use
     * @return the deserialized object
     */
    default T readObject(JSONReader jsonReader, long features) {
        return readObject(jsonReader, null, null, features);
    }

    /**
     * Reads an object from the specified JSONReader with the given field type, field name, and features.
     *
     * @param jsonReader the JSONReader to use for parsing
     * @param fieldType the type of the field being read
     * @param fieldName the name of the field being read
     * @param features the features to use
     * @return the deserialized object
     * @throws JSONException if a suitable ObjectReader is not found
     */
    T readObject(JSONReader jsonReader, Type fieldType, Object fieldName, long features);
}