SerializerAdapter.java

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.core.util.serializer;

import com.azure.core.http.HttpHeaders;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * An interface defining the behaviors of a serializer.
 */
public interface SerializerAdapter {

    /**
     * Serializes an object into a string.
     *
     * @param object the object to serialize
     * @param encoding the encoding to use for serialization
     * @return the serialized string. Null if the object to serialize is null
     * @throws IOException exception from serialization
     */
    String serialize(Object object, SerializerEncoding encoding) throws IOException;

    /**
     * Serializes an object and writes its output into an {@link OutputStream}.
     *
     * @param object The object to serialize.
     * @param encoding The encoding to use for serialization.
     * @param outputStream The {@link OutputStream} where the serialized object will be written.
     * @throws IOException exception from serialization
     */
    default void serialize(final Object object, final SerializerEncoding encoding, OutputStream outputStream)
        throws IOException {
        String serializedObject = serialize(object, encoding);

        if (serializedObject != null) {
            outputStream.write(serializedObject.getBytes(StandardCharsets.UTF_8));
        }
    }

    /**
     * Serializes an object into a raw string. The leading and trailing quotes will be trimmed.
     *
     * @param object the object to serialize
     * @return the serialized string. Null if the object to serialize is null
     */
    String serializeRaw(Object object);

    /**
     * Serializes a list into a string with the delimiter specified with the Swagger collection format joining each
     * individual serialized items in the list.
     *
     * @param list the list to serialize
     * @param format the Swagger collection format
     * @return the serialized string
     */
    String serializeList(List<?> list, CollectionFormat format);

    /**
     * Deserializes a string into a {@code T} object.
     *
     * @param value the string value to deserialize
     * @param <T> the type of the deserialized object
     * @param type the type to deserialize
     * @param encoding the encoding used in the serialized value
     * @return the deserialized object
     * @throws IOException exception from deserialization
     */
    <T> T deserialize(String value, Type type, SerializerEncoding encoding) throws IOException;

    /**
     * Deserializes a byte[] into a {@code T} object.
     *
     * @param inputStream The {@link InputStream} containing the serialized object data to deserialize.
     * @param type The type to deserialize.
     * @param encoding The encoding used to serialize value.
     * @param <T> The type of the deserialized object.
     * @return The deserialized object, or null if it cannot be deserialized.
     * @throws IOException exception from deserialization
     */
    default <T> T deserialize(final InputStream inputStream, final Type type, final SerializerEncoding encoding)
        throws IOException {
        if (inputStream == null) {
            return deserialize((String) null, type, encoding);
        }

        /*
         * Default implementation reads the entire InputStream into a ByteArrayOutputStream. This is done to enable
         * converting to a String and calling into the implemented API.
         */
        ByteArrayOutputStream converterStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int length;
        while ((length = inputStream.read(buffer)) != -1) {
            converterStream.write(buffer, 0, length);
        }

        /*
         * Using ByteArrayOutputStream.toString is better as it won't duplicate the underlying buffer as toByteArray
         * would but it doesn't have support for passing a Charset until Java 10.
         */
        return deserialize(converterStream.toString("UTF-8"), type, encoding);
    }

    /**
     * Deserialize the provided headers returned from a REST API to an entity instance declared as the model to hold
     * 'Matching' headers.
     *
     * 'Matching' headers are the REST API returned headers those with:
     *
     * <ol>
     *   <li>header names same as name of a properties in the entity.</li>
     *   <li>header names start with value of {@link com.azure.core.annotation.HeaderCollection} annotation applied to
     *   the properties in the entity.</li>
     * </ol>
     *
     * When needed, the 'header entity' types must be declared as first generic argument of
     * {@link com.azure.core.http.rest.ResponseBase} returned by java proxy method corresponding to the REST API.
     * e.g.
     * {@code Mono<RestResponseBase<FooMetadataHeaders, Void>> getMetadata(args);}
     * {@code
     *      class FooMetadataHeaders {
     *          String name;
     *          {@literal @}HeaderCollection("header-collection-prefix-")
     *          Map<String,String> headerCollection;
     *      }
     * }
     *
     * in the case of above example, this method produces an instance of FooMetadataHeaders from provided
     * {@code headers}.
     *
     * @param headers the REST API returned headers
     * @param <T> the type of the deserialized object
     * @param type the type to deserialize
     * @return instance of header entity type created based on provided {@code headers}, if header entity model does not
     * not exists then return null
     * @throws IOException If an I/O error occurs
     */
    <T> T deserialize(HttpHeaders headers, Type type) throws IOException;
}