/* * Copyright © 2013-2019, The SeedStack authors <http://seedstack.org> * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.seedstack.business.domain; import static org.seedstack.business.internal.utils.FieldUtils.resolveField; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Objects; import org.seedstack.business.internal.BusinessErrorCode; import org.seedstack.business.internal.BusinessException; import org.seedstack.business.internal.utils.FieldUtils; /** * {@link Repository} option for sorting aggregates. */ public class SortOption implements Repository.Option { private static final String ATTRIBUTE_PATH_PATTERN = "\\."; private final List<SortedAttribute> sortedAttributes = new ArrayList<>(); private final Direction defaultDirection; /** * Creates an empty sort option with {@link Direction#ASCENDING} as default sort direction. */ public SortOption() { this.defaultDirection = Direction.ASCENDING; } /** * Creates an empty sort option with the specified argument as default sort direction. * * @param defaultDirection the default sort direction. */ public SortOption(Direction defaultDirection) { this.defaultDirection = defaultDirection; } /** * Adds the specified attribute to the list of sorted attributes with the default direction. * * @param attribute the attribute to sort. * @return the sort option itself. */ public SortOption add(String attribute) { sortedAttributes.add(new SortedAttribute(attribute, defaultDirection)); return this; } /** * Adds the specified attribute to the list of sorted attributes with the specified direction. * * @param attribute the attribute to sort. * @param direction the direction this attribute will be sorted with. * @return the sort option itself. */ public SortOption add(String attribute, Direction direction) { sortedAttributes.add(new SortedAttribute(attribute, direction)); return this; } /** * Returns the sorted attributes. * * @return the list of currently registered sorted attributes. */ public List<SortedAttribute> getSortedAttributes() { return Collections.unmodifiableList(sortedAttributes); } /** * Builds a comparator allowing the sorting of objects according to the sort criteria. * * @param <T> the type of the object to compare. * @return the comparator. */ public <T> Comparator<T> buildComparator() { if (sortedAttributes.isEmpty()) { return (o1, o2) -> 0; } else { Comparator<T> comparator = null; for (SortedAttribute sortedAttribute : sortedAttributes) { if (comparator == null) { comparator = buildComparator(sortedAttribute); } else { comparator = comparator.thenComparing(buildComparator(sortedAttribute)); } } return comparator; } } private <T> Comparator<T> buildComparator(SortedAttribute sortedAttribute) { final String[] parts = sortedAttribute.getAttribute().split(ATTRIBUTE_PATH_PATTERN); Comparator<T> comparator = (t1, t2) -> { Object val1 = t1; Object val2 = t2; for (String part : parts) { val1 = accessValue(val1, part); val2 = accessValue(val2, part); } return ensureComparable(val1).compareTo(val2); }; if (sortedAttribute.getDirection() == Direction.DESCENDING) { return comparator.reversed(); } else { return comparator; } } @SuppressWarnings("unchecked") private <T> Comparable<T> ensureComparable(Object o) { if (o instanceof Comparable) { return (Comparable<T>) o; } else { throw BusinessException.createNew(BusinessErrorCode.VALUE_CANNOT_BE_COMPARED) .put("value", String.valueOf(o)) .put("valueType", o.getClass()); } } private Object accessValue(Object o, String part) { return resolveField(o.getClass(), part) .map(f -> FieldUtils.getFieldValue(o, f)) .<BusinessException>orElseThrow(() -> BusinessException.createNew(BusinessErrorCode.UNRESOLVED_FIELD) .put("className", o.getClass()) .put("fieldName", part)); } /** * Sort direction associated to a sorted attribute. */ public enum Direction { ASCENDING, DESCENDING } /** * Represents a specific sorted attribute in a {@link SortOption}. */ public static class SortedAttribute { private final String attribute; private final Direction direction; /** * Creates a sorted attribute. * * @param attribute the name of the attribute to sort. * @param direction the direction of the sort. */ SortedAttribute(String attribute, Direction direction) { this.direction = direction; this.attribute = attribute; } /** * Returns the sort direction of the attribute. * * @return the direction the attribute will be sorted with. */ public Direction getDirection() { return direction; } /** * Returns the attribute name. * * @return the sorted attribute name. */ public String getAttribute() { return attribute; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } SortedAttribute sortedAttribute = (SortedAttribute) o; return direction == sortedAttribute.direction && Objects.equals(attribute, sortedAttribute.attribute); } @Override public int hashCode() { return Objects.hash(direction, attribute); } } }