PaginatedResponseBase.java

/*
 * discogs-java-client - A Java SDK to access the Discogs API
 * Copyright © 2025 Andy Miles (andy.miles@amilesend.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package com.amilesend.discogs.model;

import com.amilesend.client.parse.parser.BasicParser;
import com.amilesend.client.parse.strategy.GsonExclude;
import com.amilesend.client.util.StringUtils;
import com.amilesend.discogs.connection.DiscogsConnection;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.SuperBuilder;
import okhttp3.HttpUrl;

import java.util.Collections;
import java.util.Map;
import java.util.Objects;

/**
 * Defines a paginated response with a limited set of results.
 *
 * @param <T> The response implementation type
 */
@SuperBuilder
@Data
public abstract class PaginatedResponseBase<T> {
    /** The underlying API connection used to navigate response pages. */
    @GsonExclude
    private final DiscogsConnection connection;
    /**
     * The pagination information.
     *
     * @see Pagination
     */
    private final Pagination pagination;

    /** Defines the method to return the implementation class type. */
    public abstract Class<T> getType();

    /**
     * Gets the first page.
     *
     * @return the first page, or {@code null} if no pages exist
     */
    public T getFirst() {
       return navigatePage(getPagination().getFirstUrl());
    }

    /**
     * Determines if there is a first page available for navigation.
     *
     * @return {@code true} if there is a first page; else, {@code false}
     */
    public boolean hasFirst() {
        return Objects.nonNull(getPagination().getFirstUrl());
    }

    /**
     * Gets the previous page.
     *
     * @return the previous page, or {@code null} if no pages exist
     */
    public T getPrevious() {
        return navigatePage(getPagination().getPreviousUrl());
    }

    /**
     * Determines if there is a previous page available for navigation.
     *
     * @return {@code true} if there is a previous page; else, {@code false}
     */
    public boolean hasPrevious() {
        return Objects.nonNull(getPagination().getPreviousUrl());
    }

    /**
     * Gets the next page.
     *
     * @return the next page, or {@code null} if no pages exist
     */
    public T getNext() {
        return navigatePage(getPagination().getNextUrl());
    }

    /**
     * Determines if there is a next page available for navigation.
     *
     * @return {@code true} if there is a next page; else, {@code false}
     */
    public boolean hasNext() {
        return Objects.nonNull(getPagination().getNextUrl());
    }

    /**
     * Gets the last page.
     *
     * @return the last page, or {@code null} if no pages exist
     */
    public T getLast() {
        return navigatePage(getPagination().getLastUrl());
    }

    /**
     * Determines if there is a last page available for navigation.
     *
     * @return {@code true} if there is a last page; else, {@code false}
     */
    public boolean hasLast() {
        return Objects.nonNull(getPagination().getLastUrl());
    }

    // Helper method to navigate pages
    private T navigatePage(final String url) {
        if (StringUtils.isBlank(url)) {
            return null;
        }

        return connection.execute(
                connection.newRequestBuilder()
                        .url(HttpUrl.parse(url))
                        .build(),
                new BasicParser<>(getType()));
    }

    /** Describes the pagination information for a response. */
    @Builder
    @Data
    public static class Pagination {
        /** The page number associated with the results. */
        private final Integer page;
        /** The total number of pages available. */
        private final Integer pages;
        /** The total number of items available. */
        private final Integer items;
        /** The number of items per page. */
        private final Integer perPage;
        /** The map of URLs used to navigate pages. */
        @Builder.Default
        private final Map<String, String> urls = Collections.emptyMap();

        /**
         * Gets the URL to navigate to the first page.
         *
         * @return the first page URL, or {@code null} if no pages exist
         */
        public String getFirstUrl() {
            return urls.get("first");
        }

        /**
         * Gets the URL for the previous page.
         *
         * @return the previous page URL, or {@code null} if no pages exist
         */
        public String getPreviousUrl() {
            return urls.get("prev");
        }

        /**
         * Gets the URL for the next page.
         *
         * @return the next page URL, or {@code null} if no pages exist
         */
        public String getNextUrl() {
            return urls.get("next");
        }

        /**
         * Gets the URL for the last page.
         *
         * @return the last page URL, or {@code null} if no pages exist
         */
        public String getLastUrl() {
            return urls.get("last");
        }
    }
}