ValueComparator.java
/*
* Copyright 2019-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.comparator;
import java.util.Comparator;
import java.util.Objects;
import java.util.Optional;
import lombok.ToString;
import org.bremersee.comparator.model.SortOrderItem;
/**
* The value comparator extracts field value of the specified field name or path and uses the
* specified description (ascending or descending, case-sensitive or insensitive and
* 'null-handling') for sorting.
*
* @author Christian Bremer
*/
@ToString
public class ValueComparator implements Comparator<Object> {
private final ValueExtractor valueExtractor;
private final SortOrderItem sortOrder;
/**
* Instantiates a new value comparator.
*
* @param sortOrder the sort order
*/
public ValueComparator(SortOrderItem sortOrder) {
this(sortOrder, null);
}
/**
* Instantiates a new value comparator.
*
* @param sortOrder the sort order
* @param valueExtractor a custom value extractor (if it is {@code null}, a default will be
* used)
*/
public ValueComparator(
SortOrderItem sortOrder,
ValueExtractor valueExtractor) {
this.sortOrder = Objects.requireNonNullElseGet(sortOrder, () -> SortOrderItem.by(null));
this.valueExtractor = valueExtractor != null ? valueExtractor : new DefaultValueExtractor();
}
@Override
public int compare(Object o1, Object o2) {
final Object v1 = valueExtractor.findValue(o1, sortOrder.getField());
final Object v2 = valueExtractor.findValue(o2, sortOrder.getField());
return compareNullSafe(v1, v2)
.or(() -> compareNonNull(v1, v2))
.orElseThrow(() -> new ComparatorException(
"Comparison of field '" + sortOrder.getField() + "' is not possible."));
}
private Optional<Integer> compareNullSafe(Object v1, Object v2) {
if (v1 == null && v2 == null) {
return Optional.of(0);
}
if (v1 == null) {
return Optional.of(firstIsNull());
}
if (v2 == null) {
return Optional.of(-1 * firstIsNull());
}
return Optional.empty();
}
private int firstIsNull() {
int r;
if (sortOrder.getDirection().isAscending()) {
r = sortOrder.getNullHandling().isNullFirst() ? -1 : 1;
} else {
r = sortOrder.getNullHandling().isNullFirst() ? 1 : -1;
}
return r;
}
@SuppressWarnings("rawtypes")
private Optional<Integer> compareNonNull(Object v1, Object v2) {
if (sortOrder.getDirection().isAscending() && v1 instanceof Comparable c1) {
if (sortOrder.getCaseHandling().isInsensitive()
&& v1 instanceof String s1 && v2 instanceof String s2) {
return Optional.of(s1.compareToIgnoreCase(s2));
} else {
//noinspection unchecked
return Optional.of(c1.compareTo(v2));
}
} else if (!sortOrder.getDirection().isAscending() && v2 instanceof Comparable c2) {
if (sortOrder.getCaseHandling().isInsensitive()
&& v1 instanceof String s1 && v2 instanceof String s2) {
return Optional.of(s2.compareToIgnoreCase(s1));
} else {
//noinspection unchecked
return Optional.of(c2.compareTo(v1));
}
}
return Optional.empty();
}
}