blob: 3727386d42e9e466df7928828985a3a76fa2cd83 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.sis.internal.filter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.Comparator;
import java.io.Serializable;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.internal.util.UnmodifiableArrayList;
// Branch-dependent imports
import org.opengis.filter.SortBy;
import org.opengis.filter.SortProperty;
import org.opengis.filter.ValueReference;
/**
* Comparator sorting features using an array of {@link SortProperty} elements applied in order.
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
* @version 1.1
*
* @param <R> the type of resources (typically {@code Feature}) to sort.
*
* @since 1.0
* @module
*/
public final class SortByComparator<R> implements SortBy<R>, Serializable {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = -7964849249532212389L;
/**
* The sort order specified to the constructor.
*
* @see #getSortProperties()
*/
private final SortProperty<R>[] properties;
/**
* Creates a new comparator for the given sort expression.
* This is shortcut for the common case where there is a single expression.
*
* @param p the property to wrap in a {@link SortBy} comparator.
*/
@SuppressWarnings({"unchecked","rawtypes"}) // Generic array creation.
private SortByComparator(final SortProperty<R> p) {
ArgumentChecks.ensureNonNullElement("properties", 0, p);
properties = new SortProperty[] {p};
}
/**
* Creates a new comparator with the values of given map.
* The map is used for removing duplicated expressions.
*/
@SuppressWarnings({"unchecked","rawtypes"}) // Generic array creation.
private SortByComparator(final Map<?, SortProperty<R>> merged) {
properties = merged.values().toArray(new SortProperty[merged.size()]);
}
/**
* Creates a new comparator for the given sort expressions.
*
* @param <R> the type of resources (typically {@code Feature}) to sort.
* @param properties the sort order.
* @return the comparator, or {@code null} if the given array is empty.
*/
public static <R> SortByComparator<R> create(final SortProperty<R>[] properties) {
switch (properties.length) {
case 0: return null;
case 1: return new SortByComparator<>(properties[0]);
}
final Map<ValueReference<? super R, ?>, SortProperty<R>> merged = new LinkedHashMap<>();
addAll(Arrays.asList(properties), merged);
return new SortByComparator<>(merged);
}
/**
* Creates a new comparator as the concatenation of the two given comparators.
* The first comparator is used first, and if two resources are equal then the
* second comparator is used.
*
* @param <R> the type of resources (typically {@code Feature}) to sort.
* @param sort the first "sort by" to use, or {@code null} if none.
* @param comparator the second "sort by" to use.
* @return concatenation of the two comparators.
*/
@SuppressWarnings("unchecked")
public static <R> SortBy<? super R> concatenate(final SortBy<? super R> sort, final Comparator<? super R> comparator) {
final SortBy<? super R> other;
if (comparator instanceof SortBy<?>) {
other = (SortBy<? super R>) comparator;
} else if (comparator instanceof SortProperty<?>) {
other = new SortByComparator<>((SortProperty<? super R>) comparator);
} else {
return null;
}
if (sort == null) {
return other;
}
/*
* The (SortBy<R>) casts are unsafe — they should be (SortBy<? super R>) — but are okay in this context
* because we create a `SortByComparator` which will only "push" instances of R to the comparators.
* There is no code that "pull" instances of R from the comparators, consequently no code that may
* be surprised to get an instance of a super type of R instead of R.
*/
return concatenate((SortBy<R>) sort, (SortBy<R>) other);
}
/**
* Creates a new comparator as the concatenation of the two given comparators.
* The first comparator is used first, and if two resources are equal then the
* second comparator is used.
*
* @param <R> the type of resources (typically {@code Feature}) to sort.
* @param s1 the first "sort by" to use.
* @param s2 the second "sort by" to use.
* @return concatenation of the two comparators.
*/
public static <R> SortBy<R> concatenate(final SortBy<R> s1, final SortBy<R> s2) {
final Map<ValueReference<? super R, ?>, SortProperty<R>> merged = new LinkedHashMap<>();
addAll(s1.getSortProperties(), merged);
addAll(s2.getSortProperties(), merged);
return new SortByComparator<>(merged);
}
/**
* Adds all elements of the {@code properties} list into the {@code merged} map.
* If two {@code SortProperty} instances use the same {@link ValueReference},
* then only the first occurrence is retained.
*/
private static <R> void addAll(final List<SortProperty<R>> properties,
final Map<ValueReference<? super R, ?>, SortProperty<R>> merged)
{
final int size = properties.size();
for (int i=0; i<size; i++) {
final SortProperty<R> p = properties.get(i);
ArgumentChecks.ensureNonNullElement("properties", i, p);
merged.putIfAbsent(p.getValueReference(), p);
}
}
/**
* Returns the properties whose values are used for sorting.
* The list shall have a minimum of one element.
*/
@Override
public List<SortProperty<R>> getSortProperties() {
return UnmodifiableArrayList.wrap(properties);
}
/**
* Compares two resources for order. Returns a negative number if {@code r1} should be sorted before {@code r2},
* a positive number if {@code r2} should be after {@code r1}, or 0 if both resources are equal.
* The ordering of null resources or null property values is unspecified.
*/
@Override
public int compare(final R r1, final R r2) {
for (final SortProperty<R> p : properties) {
final int c = p.compare(r1, r2);
if (c != 0) return c;
}
return 0;
}
/**
* Returns a comparator as the concatenation of this comparator with the given one.
* This comparator is used first, and if two resources are equal then the other comparator is used.
*
* @param other the other comparator to be used when this comparator considers two resources as equal.
* @return concatenation of this comparator with the given one.
*/
@Override
@SuppressWarnings("unchecked")
public Comparator<R> thenComparing​(final Comparator<? super R> other) {
if (other instanceof SortBy<?>) {
/*
* The (SortBy<R>) cast is unsafe — it should be (SortBy<? super R>) — but it is okay in this context
* because we create a `SortByComparator` which will only "push" instances of R to the `other` comparator.
* There is no code that "pull" instances of R from the `other` comparator, consequently no code that may
* be surprised to get an instance of a super type of R instead of R.
*/
return concatenate(this, (SortBy<R>) other);
}
return SortBy.super.thenComparing​(other);
}
}