001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.util.comparator;
018
019import java.io.Serializable;
020import java.util.ArrayList;
021import java.util.Comparator;
022import java.util.List;
023
024import org.springframework.util.Assert;
025
026/**
027 * A comparator that chains a sequence of one or more Comparators.
028 *
029 * <p>A compound comparator calls each Comparator in sequence until a single
030 * Comparator returns a non-zero result, or the comparators are exhausted and
031 * zero is returned.
032 *
033 * <p>This facilitates in-memory sorting similar to multi-column sorting in SQL.
034 * The order of any single Comparator in the list can also be reversed.
035 *
036 * @author Keith Donald
037 * @author Juergen Hoeller
038 * @since 1.2.2
039 */
040@SuppressWarnings({"serial", "rawtypes"})
041public class CompoundComparator<T> implements Comparator<T>, Serializable {
042
043        private final List<InvertibleComparator> comparators;
044
045
046        /**
047         * Construct a CompoundComparator with initially no Comparators. Clients
048         * must add at least one Comparator before calling the compare method or an
049         * IllegalStateException is thrown.
050         */
051        public CompoundComparator() {
052                this.comparators = new ArrayList<InvertibleComparator>();
053        }
054
055        /**
056         * Construct a CompoundComparator from the Comparators in the provided array.
057         * <p>All Comparators will default to ascending sort order,
058         * unless they are InvertibleComparators.
059         * @param comparators the comparators to build into a compound comparator
060         * @see InvertibleComparator
061         */
062        @SuppressWarnings("unchecked")
063        public CompoundComparator(Comparator... comparators) {
064                Assert.notNull(comparators, "Comparators must not be null");
065                this.comparators = new ArrayList<InvertibleComparator>(comparators.length);
066                for (Comparator comparator : comparators) {
067                        addComparator(comparator);
068                }
069        }
070
071
072        /**
073         * Add a Comparator to the end of the chain.
074         * <p>The Comparator will default to ascending sort order,
075         * unless it is a InvertibleComparator.
076         * @param comparator the Comparator to add to the end of the chain
077         * @see InvertibleComparator
078         */
079        @SuppressWarnings("unchecked")
080        public void addComparator(Comparator<? extends T> comparator) {
081                if (comparator instanceof InvertibleComparator) {
082                        this.comparators.add((InvertibleComparator) comparator);
083                }
084                else {
085                        this.comparators.add(new InvertibleComparator(comparator));
086                }
087        }
088
089        /**
090         * Add a Comparator to the end of the chain using the provided sort order.
091         * @param comparator the Comparator to add to the end of the chain
092         * @param ascending the sort order: ascending (true) or descending (false)
093         */
094        @SuppressWarnings("unchecked")
095        public void addComparator(Comparator<? extends T> comparator, boolean ascending) {
096                this.comparators.add(new InvertibleComparator(comparator, ascending));
097        }
098
099        /**
100         * Replace the Comparator at the given index.
101         * <p>The Comparator will default to ascending sort order,
102         * unless it is a InvertibleComparator.
103         * @param index the index of the Comparator to replace
104         * @param comparator the Comparator to place at the given index
105         * @see InvertibleComparator
106         */
107        @SuppressWarnings("unchecked")
108        public void setComparator(int index, Comparator<? extends T> comparator) {
109                if (comparator instanceof InvertibleComparator) {
110                        this.comparators.set(index, (InvertibleComparator) comparator);
111                }
112                else {
113                        this.comparators.set(index, new InvertibleComparator(comparator));
114                }
115        }
116
117        /**
118         * Replace the Comparator at the given index using the given sort order.
119         * @param index the index of the Comparator to replace
120         * @param comparator the Comparator to place at the given index
121         * @param ascending the sort order: ascending (true) or descending (false)
122         */
123        public void setComparator(int index, Comparator<T> comparator, boolean ascending) {
124                this.comparators.set(index, new InvertibleComparator<T>(comparator, ascending));
125        }
126
127        /**
128         * Invert the sort order of each sort definition contained by this compound
129         * comparator.
130         */
131        public void invertOrder() {
132                for (InvertibleComparator comparator : this.comparators) {
133                        comparator.invertOrder();
134                }
135        }
136
137        /**
138         * Invert the sort order of the sort definition at the specified index.
139         * @param index the index of the comparator to invert
140         */
141        public void invertOrder(int index) {
142                this.comparators.get(index).invertOrder();
143        }
144
145        /**
146         * Change the sort order at the given index to ascending.
147         * @param index the index of the comparator to change
148         */
149        public void setAscendingOrder(int index) {
150                this.comparators.get(index).setAscending(true);
151        }
152
153        /**
154         * Change the sort order at the given index to descending sort.
155         * @param index the index of the comparator to change
156         */
157        public void setDescendingOrder(int index) {
158                this.comparators.get(index).setAscending(false);
159        }
160
161        /**
162         * Returns the number of aggregated comparators.
163         */
164        public int getComparatorCount() {
165                return this.comparators.size();
166        }
167
168        @Override
169        @SuppressWarnings("unchecked")
170        public int compare(T o1, T o2) {
171                Assert.state(this.comparators.size() > 0,
172                                "No sort definitions have been added to this CompoundComparator to compare");
173                for (InvertibleComparator comparator : this.comparators) {
174                        int result = comparator.compare(o1, o2);
175                        if (result != 0) {
176                                return result;
177                        }
178                }
179                return 0;
180        }
181
182        @Override
183        @SuppressWarnings("unchecked")
184        public boolean equals(Object obj) {
185                if (this == obj) {
186                        return true;
187                }
188                if (!(obj instanceof CompoundComparator)) {
189                        return false;
190                }
191                CompoundComparator<T> other = (CompoundComparator<T>) obj;
192                return this.comparators.equals(other.comparators);
193        }
194
195        @Override
196        public int hashCode() {
197                return this.comparators.hashCode();
198        }
199
200        @Override
201        public String toString() {
202                return "CompoundComparator: " + this.comparators;
203        }
204
205}