PageBuilder.java

/*
 * Copyright 2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.bremersee.pagebuilder;

import static java.util.Objects.requireNonNullElse;

import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.bremersee.comparator.ComparatorBuilder;
import org.bremersee.comparator.ValueComparator;
import org.bremersee.comparator.model.SortOrder;
import org.bremersee.comparator.spring.mapper.SortMapper;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.util.ObjectUtils;

/**
 * The page builder.
 *
 * @param <S> the source type
 * @param <T> the target type
 * @author Christian Bremer
 */
public class PageBuilder<S, T> {

  private Stream<? extends S> sourceEntries;

  private Predicate<S> sourceFilter;

  private Function<SortOrder, Comparator<?>> sourceSortFn;

  private Integer pageNumber;

  private Integer pageSize;

  private List<SortOrder> sort;

  private SortTarget sortTarget;

  private Function<S, T> converter;

  private Predicate<T> targetFilter;

  private Function<SortOrder, Comparator<?>> targetSortFn;

  /**
   * Instantiates a new page builder.
   */
  public PageBuilder() {
    sourceFilter = sourceEntry -> true;
    //noinspection unchecked
    sourceSortFn = cf -> (Comparator<S>) new ValueComparator(cf);
    //noinspection unchecked
    converter = e -> (T) e;
    targetFilter = targetEntry -> true;
    //noinspection unchecked
    targetSortFn = cf -> (Comparator<T>) new ValueComparator(cf);
  }

  /**
   * Sets source entries to the page builder.
   *
   * @param entries the entries
   * @return the page builder
   */
  public PageBuilder<S, T> sourceEntries(Stream<? extends S> entries) {
    if (!Objects.isNull(entries)) {
      this.sourceEntries = entries;
    }
    return this;
  }

  /**
   * Sets source entries to the page builder.
   *
   * @param entries the entries
   * @return the page builder
   */
  public PageBuilder<S, T> sourceEntries(Iterable<? extends S> entries) {
    if (!Objects.isNull(entries)) {
      this.sourceEntries = StreamSupport.stream(entries.spliterator(), false);
    }
    return this;
  }

  /**
   * Sets source entries to the page builder.
   *
   * @param entries the entries
   * @return the page builder
   */
  public PageBuilder<S, T> sourceEntries(Iterator<? extends S> entries) {
    if (!Objects.isNull(entries)) {
      this.sourceEntries = StreamSupport.stream(Spliterators
          .spliteratorUnknownSize(entries, Spliterator.ORDERED), false);
    }
    return this;
  }

  /**
   * Sets source filter to the page builder.
   *
   * @param sourceFilter the source filter
   * @return the page builder
   */
  public PageBuilder<S, T> sourceFilter(Predicate<S> sourceFilter) {
    if (!Objects.isNull(sourceFilter)) {
      this.sourceFilter = sourceFilter;
    }
    return this;
  }

  /**
   * Sets source sort function to the page builder.
   *
   * @param sourceSortFn the source sort function
   * @return the page builder
   */
  public PageBuilder<S, T> sourceSortFn(
      Function<SortOrder, Comparator<?>> sourceSortFn) {
    if (!Objects.isNull(sourceSortFn)) {
      this.sourceSortFn = sourceSortFn;
    }
    return this;
  }

  /**
   * Sets pageable to the page builder.
   *
   * @param pageable the pageable
   * @return the page builder
   */
  public PageBuilder<S, T> pageable(Pageable pageable) {
    return pageable(pageable, SortTarget.TARGET_ENTRIES);
  }

  /**
   * Sets pageable to the page builder.
   *
   * @param pageable the pageable
   * @param sortTarget the sort target
   * @return the page builder
   */
  public PageBuilder<S, T> pageable(Pageable pageable, SortTarget sortTarget) {
    if (!Objects.isNull(pageable)) {
      return pageable(
          pageable.getPageNumber(),
          pageable.getPageSize(),
          sortTarget,
          SortMapper.fromSort(pageable.getSort()));
    }
    return this;
  }

  /**
   * Sets pageable to the page builder.
   *
   * @param pageNumber the page number
   * @param pageSize the page size
   * @param sort the sort
   * @return the page builder
   */
  public PageBuilder<S, T> pageable(
      Integer pageNumber,
      Integer pageSize,
      SortOrder... sort) {
    return pageable(
        pageNumber,
        pageSize,
        SortTarget.TARGET_ENTRIES,
        Objects.isNull(sort) ? null : Arrays.asList(sort));
  }

  /**
   * Sets pageable to the page builder.
   *
   * @param pageNumber the page number
   * @param pageSize the page size
   * @param sort the sort
   * @return the page builder
   */
  public PageBuilder<S, T> pageable(
      Integer pageNumber,
      Integer pageSize,
      List<SortOrder> sort) {
    return pageable(pageNumber, pageSize, SortTarget.TARGET_ENTRIES, sort);
  }

  /**
   * Sets pageable to the page builder.
   *
   * @param pageNumber the page number
   * @param pageSize the page size
   * @param sortTarget the sort target
   * @param sort the sort
   * @return the page builder
   */
  public PageBuilder<S, T> pageable(
      Integer pageNumber,
      Integer pageSize,
      SortTarget sortTarget,
      SortOrder... sort) {
    return pageable(
        pageNumber,
        pageSize,
        sortTarget,
        Objects.isNull(sort) ? null : Arrays.asList(sort));
  }

  /**
   * Sets pageable to the page builder.
   *
   * @param pageNumber the page number
   * @param pageSize the page size
   * @param sortTarget the sort target
   * @param sort the sort
   * @return the page builder
   */
  public PageBuilder<S, T> pageable(
      Integer pageNumber,
      Integer pageSize,
      SortTarget sortTarget,
      List<SortOrder> sort) {

    Pageable pageable = PageRequest.of(pageNumber, pageSize);
    this.pageNumber = pageable.getPageNumber();
    this.pageSize = pageable.getPageSize();
    this.sort = sort;
    if (ObjectUtils.isEmpty(this.sort)) {
      this.sortTarget = null;
    } else {
      this.sortTarget = requireNonNullElse(sortTarget, SortTarget.TARGET_ENTRIES);
    }
    return this;
  }

  /**
   * Sets entry converter to the page builder.
   *
   * @param converter the entry converter
   * @return the page builder
   */
  public PageBuilder<S, T> converter(Function<S, T> converter) {
    if (!Objects.isNull(converter)) {
      this.converter = converter;
    }
    return this;
  }

  /**
   * Sets target filter to the page builder.
   *
   * @param targetFilter the target filter
   * @return the page builder
   */
  public PageBuilder<S, T> targetFilter(Predicate<T> targetFilter) {
    if (!Objects.isNull(targetFilter)) {
      this.targetFilter = targetFilter;
    }
    return this;
  }

  /**
   * Sets target sort function to the page builder.
   *
   * @param targetSortFn the target sort function
   * @return the page builder
   */
  public PageBuilder<S, T> targetSortFn(
      Function<SortOrder, Comparator<?>> targetSortFn) {
    if (!Objects.isNull(targetSortFn)) {
      this.targetSortFn = targetSortFn;
    }
    return this;
  }

  /**
   * Builds the page.
   *
   * @return the page
   */
  public Page<T> build() {
    //noinspection unchecked
    final List<S> source = ((Stream<S>) requireNonNullElse(this.sourceEntries, Stream.empty()))
        .filter(sourceFilter)
        .collect(Collectors.toList());
    if (SortTarget.SOURCE_ENTRIES.equals(sortTarget) && !ObjectUtils.isEmpty(sort)) {
      source.sort(ComparatorBuilder.newInstance()
          .addAll(sort, sourceSortFn)
          .build());
    }
    final List<T> target = source.stream()
        .map(converter)
        .filter(targetFilter)
        .collect(Collectors.toList());
    final Sort pageSort;
    if (SortTarget.TARGET_ENTRIES.equals(sortTarget) && !ObjectUtils.isEmpty(sort)) {
      target.sort(ComparatorBuilder.newInstance()
          .addAll(sort, targetSortFn)
          .build());
      pageSort = SortMapper.toSort(sort);
    } else {
      pageSort = Sort.unsorted();
    }

    int number = requireNonNullElse(pageNumber, 0);
    int size = requireNonNullElse(pageSize, Integer.MAX_VALUE);
    final Pageable pageable = PageRequest.of(number, size, pageSort);
    final List<T> content = target.stream()
        .skip(pageable.getOffset())
        .limit(pageable.getPageSize())
        .collect(Collectors.toList());
    return new PageImpl<>(content, pageable, target.size());
  }

  /**
   * The sort target.
   */
  public enum SortTarget {
    /**
     * Source entries sort target.
     */
    SOURCE_ENTRIES,
    /**
     * Target entries sort target.
     */
    TARGET_ENTRIES
  }
}