TypeToken.java

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package software.amazon.lambda.durable;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
 * Framework-agnostic type token for capturing generic type information at runtime.
 *
 * <p>This class enables type-safe deserialization of complex generic types like {@code List<MyObject>} or
 * {@code Map<String, MyObject>} that would otherwise lose their type information due to Java's type erasure.
 *
 * <p>Usage example:
 *
 * <pre>{@code
 * // Capture generic type information
 * TypeToken<List<String>> token = new TypeToken<List<String>>() {};
 *
 * // Use with DurableContext
 * List<String> items = context.step("fetch-items",
 *     new TypeToken<List<String>>() {},
 *     stepCtx -> fetchItems());
 * }</pre>
 *
 * @param <T> the type being captured
 */
public abstract class TypeToken<T> {
    private final Type type;

    /**
     * Constructs a new TypeToken. This constructor must be called from an anonymous subclass to capture the type
     * parameter.
     *
     * @throws IllegalStateException if created without a type parameter
     */
    protected TypeToken() {
        Type superClass = getClass().getGenericSuperclass();
        if (superClass instanceof ParameterizedType) {
            this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
        } else {
            throw new IllegalStateException("TypeToken must be created as an anonymous subclass with a type parameter. "
                    + "Example: new TypeToken<List<String>>() {}");
        }
    }

    private TypeToken(Type type) {
        this.type = type;
    }

    public static <U> TypeToken<U> get(Class<U> clazz) {
        return new TypeToken<>(clazz) {};
    }

    /**
     * Returns the captured type.
     *
     * @return the type represented by this token
     */
    public Type getType() {
        return type;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof TypeToken)) return false;
        TypeToken<?> other = (TypeToken<?>) obj;
        return type.equals(other.type);
    }

    @Override
    public int hashCode() {
        return type.hashCode();
    }

    @Override
    public String toString() {
        return "TypeToken<" + type.getTypeName() + ">";
    }
}