All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.commercetools.sunrise.search.facetedsearch.RangeUtils Maven / Gradle / Ivy

The newest version!
package com.commercetools.sunrise.search.facetedsearch;

import io.sphere.sdk.search.RangeFacetResult;
import io.sphere.sdk.search.model.FacetRange;
import io.sphere.sdk.search.model.FilterRange;
import io.sphere.sdk.search.model.RangeStats;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;

import javax.annotation.Nullable;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class RangeUtils {

    private static final Pattern PATTERN = Pattern.compile("^\\(\\s*(?\\S+)\\s+to\\s+(?\\S+)\\s*\\)$");

    private RangeUtils() {
    }

    public static Map mapRangeToStats(final RangeFacetResult facetResult) {
        final Map rangeToStats = new HashMap<>();
        facetResult.getRanges()
                .forEach(stats -> {
                    final String lowerEndpoint = cleanEndpointDecimal(stats.getLowerEndpoint());
                    final String upperEndpoint = cleanEndpointDecimal(stats.getUpperEndpoint());
                    parseFacetRange(lowerEndpoint, upperEndpoint)
                            .ifPresent(range -> rangeToStats.put(range.toString(), stats));
                });
        return rangeToStats;
    }

    public static Optional> parseFilterRange(@Nullable final String lowerEndpoint, @Nullable final String upperEndpoint) {
        final String rangeAsString = buildRange(lowerEndpoint, upperEndpoint);
        return parseFilterRange(rangeAsString);
    }

    public static Optional> parseFacetRange(@Nullable final String lowerEndpoint, @Nullable final String upperEndpoint) {
        final String rangeAsString = buildRange(lowerEndpoint, upperEndpoint);
        return parseFacetRange(rangeAsString);
    }

    /**
     * Parses a range of the form {@code (x to y)} to a {@link FilterRange}.
     * Ranges of the form {@code (* to *)} are not supported.
     * @param rangeAsString range of the form {@code (x to y)}
     * @return the {@link FilterRange} corresponding to that range, or empty if it could not be parsed
     */
    public static Optional> parseFilterRange(final String rangeAsString) {
        return Optional.ofNullable(parseRangeToPair(rangeAsString))
                .filter(pair -> pair.getRight() != null || pair.getLeft() != null)
                .map(pair -> {
                    if (pair.getLeft() != null && pair.getRight() != null) {
                        return FilterRange.of(pair.getLeft(), pair.getRight());
                    } else if (pair.getLeft() != null) {
                        return FilterRange.atLeast(pair.getLeft());
                    } else {
                        return FilterRange.atMost(pair.getRight());
                    }
                })
                .filter(RangeUtils::hasNoBounds);
    }

    /**
     * Parses a range of the form {@code (x to y)} to a {@link FacetRange}.
     * Ranges of the form {@code (* to *)} are not supported.
     * @param rangeAsString range of the form {@code (x to y)}
     * @return the {@link FacetRange} corresponding to that range, or empty if it could not be parsed
     */
    public static Optional> parseFacetRange(final String rangeAsString) {
        return Optional.ofNullable(parseRangeToPair(rangeAsString))
                .filter(pair -> pair.getRight() != null || pair.getLeft() != null)
                .map(pair -> {
                    if (pair.getLeft() != null && pair.getRight() != null) {
                        return FacetRange.of(pair.getLeft(), pair.getRight());
                    } else if (pair.getLeft() != null) {
                        return FacetRange.atLeast(pair.getLeft());
                    } else {
                        return FacetRange.lessThan(pair.getRight());
                    }
                })
                .filter(RangeUtils::hasNoBounds);
    }

    @Nullable
    private static Pair parseRangeToPair(final String rangeAsString) {
        final Matcher matcher = PATTERN.matcher(rangeAsString.trim());
        if (matcher.matches()) {
            final String lowerEndpoint = matcher.group("lower");
            final String upperEndpoint = matcher.group("upper");
            return ImmutablePair.of(boundEndpointOrNull(lowerEndpoint), boundEndpointOrNull(upperEndpoint));
        }
        return null;
    }

    private static String buildRange(@Nullable final String lowerEndpoint, @Nullable final String upperEndpoint) {
        return String.format("(%s to %s)",
                boundEndpointOrAsterisk(lowerEndpoint),
                boundEndpointOrAsterisk(upperEndpoint));
    }

    @Nullable
    private static String boundEndpointOrNull(@Nullable final String endpoint) {
        return isEndpointBoundless(endpoint) ? null : endpoint;
    }

    private static String boundEndpointOrAsterisk(@Nullable final String endpoint) {
        return isEndpointBoundless(endpoint) ? "*" : endpoint;
    }

    private static boolean isEndpointBoundless(@Nullable final String endpoint) {
        return endpoint == null || endpoint.equals("*");
    }

    private static > boolean hasNoBounds(final FacetRange range) {
        return range.lowerBound() != null || range.upperBound() != null;
    }

    private static > boolean hasNoBounds(final FilterRange range) {
        return range.lowerBound() != null || range.upperBound() != null;
    }

    /**
     * Cleans the decimal part of the endpoint value due to the backend answering with decimals but not accepting them.
     * For example, the backend might answer with 9002.0, but it needs to be converted to 9002.
     * @param endpointValue the value of the endpoint to be cleaned
     * @return the value of the endpoint without the decimal part, in case the value was a number
     */
    @Nullable
    private static String cleanEndpointDecimal(@Nullable final String endpointValue) {
        if (endpointValue != null) {
            try {
                final int value = new BigDecimal(endpointValue).intValue();
                return String.valueOf(value);
            } catch (NumberFormatException e) {
                // Fallbacks to default case
            }
        }
        return endpointValue;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy