| /***************************************************************************** |
| * 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; |
| } |
| |
| } |