blob: 1b3426a0fb4a69cd164a0ac24dd12b25849d57d9 [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2018, 2020 Dirk Fauth.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Dirk Fauth <dirk.fauth@googlemail.com> - Initial API and implementation
*****************************************************************************/
package org.eclipse.nebula.widgets.nattable.hierarchical;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import org.eclipse.nebula.widgets.nattable.config.DefaultComparator;
import org.eclipse.nebula.widgets.nattable.data.IColumnAccessor;
import org.eclipse.nebula.widgets.nattable.sort.ISortModel;
import org.eclipse.nebula.widgets.nattable.sort.SortDirectionEnum;
/**
* {@link Comparator} for collections of {@link HierarchicalWrapper}. Ensures
* that the objects are ordered in a way that the expected tree structure is
* kept. Additionally provides a way to sort by column if an {@link ISortModel}
* is set, by still keeping the tree structure. That means in deeper levels of
* the tree, only the sub nodes are sorted, not the tree itself is changed.
*
* @since 1.6
*/
public class HierarchicalWrapperComparator implements Comparator<HierarchicalWrapper> {
private IColumnAccessor<HierarchicalWrapper> columnAccessor;
private Map<Integer, List<Integer>> levelIndexMapping;
private ISortModel sortModel;
/**
* Creates a new {@link HierarchicalWrapperComparator} without an
* {@link ISortModel}. Without an {@link ISortModel} the
* {@link DefaultComparator} is used for comparison of column values to keep
* the tree structure. The sort order can not be influenced via column
* sorting, until a {@link ISortModel} is set via
* {@link #setSortModel(ISortModel)}.
*
* @param columnAccessor
* The {@link IColumnAccessor} needed to access the column values
* of the {@link HierarchicalWrapper} row objects.
* @param levelIndexMapping
* The mapping of tree level to column indexes needed for level
* based sorting.
*/
public HierarchicalWrapperComparator(
IColumnAccessor<HierarchicalWrapper> columnAccessor,
Map<Integer, List<Integer>> levelIndexMapping) {
this(columnAccessor, levelIndexMapping, null);
}
/**
* Creates a new {@link HierarchicalWrapperComparator} with an
* {@link ISortModel} to support dynamic configurable column based sorting.
*
* @param columnAccessor
* The {@link IColumnAccessor} needed to access the column values
* of the {@link HierarchicalWrapper} row objects.
* @param levelIndexMapping
* The mapping of tree level to column indexes needed for level
* based sorting.
* @param sortModel
* The {@link ISortModel} that provides access to configured
* column comparators and supports dynamic sorting per column.
*/
public HierarchicalWrapperComparator(
IColumnAccessor<HierarchicalWrapper> columnAccessor,
Map<Integer, List<Integer>> levelIndexMapping,
ISortModel sortModel) {
this.columnAccessor = columnAccessor;
this.levelIndexMapping = levelIndexMapping;
this.sortModel = sortModel;
}
@Override
public int compare(HierarchicalWrapper o1, HierarchicalWrapper o2) {
if (o1 == null && o2 == null) {
return 0;
} else if (o1 == null && o2 != null) {
return -1;
} else if (o2 == null) {
return 1;
} else if (o1 == o2) {
return 0;
} else {
// different levels = different objects based on levels supported
if (o1.getLevels() != o2.getLevels()) {
return o1.getLevels() - o2.getLevels();
} else {
// first perform the level comparison to keep the tree structure
int level = 0;
Object o1LvlObject = o1.getObject(level);
Object o2LvlObject = o2.getObject(level);
// find the level objects that differ
// do not consider the leaf level as it is not important for the
// tree structure
while (o1LvlObject == o2LvlObject && level < (this.levelIndexMapping.size())) {
level++;
o1LvlObject = o1.getObject(level);
o2LvlObject = o2.getObject(level);
}
int result = 0;
if (o1LvlObject == null && o2LvlObject != null) {
result = -1;
} else if (o2LvlObject == null) {
result = 1;
} else if (o1LvlObject != null && o2LvlObject != null) {
// compare level objects
result = compareLevel(o1, o2, level);
}
if (result < 0) {
result = -1;
} else if (result > 0) {
result = 1;
}
return result;
}
}
}
/**
* Compares two {@link HierarchicalWrapper} objects based on the information
* in the given level.
*
* @param o1
* The first {@link HierarchicalWrapper} object to be compared.
* @param o2
* The second {@link HierarchicalWrapper} object to be compared.
* @param level
* The level that should be compared.
* @return a negative integer, zero, or a positive integer as the first
* argument is less than, equal to, or greater than the second.
*/
protected int compareLevel(HierarchicalWrapper o1, HierarchicalWrapper o2, int level) {
List<Integer> levelIndexes = this.levelIndexMapping.get(level);
int result = 0;
if (this.sortModel != null) {
List<Integer> sortedColumnIndexes = this.sortModel.getSortedColumnIndexes();
for (Integer sorted : sortedColumnIndexes) {
if (levelIndexes.contains(sorted)) {
result = compareColumn(o1, o2, sorted, false);
if (result != 0) {
break;
}
}
}
}
return result;
}
/**
* Compares two {@link HierarchicalWrapper} objects based on the information
* in the given column.
*
* @param o1
* The first {@link HierarchicalWrapper} object to be compared.
* @param o2
* The second {@link HierarchicalWrapper} object to be compared.
* @param columnIndex
* @param useDefault
* flag to configure whether the {@link DefaultComparator} should
* be used in case no dedicated comparator is configured. Should
* be set to <code>true</code> in case the comparator for a
* column is requested that is needed to ensure the tree
* structure.
* @return a negative integer, zero, or a positive integer as the first
* argument is less than, equal to, or greater than the second.
*/
@SuppressWarnings("unchecked")
protected int compareColumn(HierarchicalWrapper o1, HierarchicalWrapper o2, int columnIndex, boolean useDefault) {
Object value1 = this.columnAccessor.getDataValue(o1, columnIndex);
Object value2 = this.columnAccessor.getDataValue(o2, columnIndex);
int result = getComparator(columnIndex, useDefault).compare(value1, value2);
if (result != 0
&& this.sortModel != null
&& this.sortModel.getSortDirection(columnIndex).equals(SortDirectionEnum.DESC)) {
result *= -1;
}
return result;
}
/**
* Returns the {@link Comparator} that should be used to compare the values
* in a column for the given column index.
*
* @param columnIndex
* The column index of the column for which the
* {@link Comparator} is requested.
* @param useDefault
* flag to configure whether the {@link DefaultComparator} should
* be used in case no dedicated comparator is configured. Should
* be set to <code>true</code> in case the comparator for a
* column is requested that is needed to ensure the tree
* structure.
* @return The {@link Comparator} that should be used to compare the values
* for elements in the given column. Returns the
* {@link DefaultComparator} in case there is no {@link ISortModel}
* configured, no {@link Comparator} is found for the given column
* or a NullComparator is configured for the given column.
*/
@SuppressWarnings("rawtypes")
protected Comparator getComparator(int columnIndex, boolean useDefault) {
Comparator result = null;
if (this.sortModel != null) {
result = this.sortModel.getColumnComparator(columnIndex);
}
if (result == null && useDefault) {
result = DefaultComparator.getInstance();
}
return result;
}
/**
*
* @return The {@link ISortModel} that is set in this
* {@link HierarchicalWrapperComparator} to support dynamic column
* based sorting in combination with tree sorting.
*/
public ISortModel getSortModel() {
return this.sortModel;
}
/**
*
* @param sortModel
* The {@link ISortModel} that should be used by this
* {@link HierarchicalWrapperComparator} to support dynamic
* column based sorting in combination with tree sorting.
*/
public void setSortModel(ISortModel sortModel) {
this.sortModel = sortModel;
}
}