/** * OrbisGIS is a java GIS application dedicated to research in GIScience. * OrbisGIS is developed by the GIS group of the DECIDE team of the * Lab-STICC CNRS laboratory, see <http://www.lab-sticc.fr/>. * * The GIS group of the DECIDE team is located at : * * Laboratoire Lab-STICC – CNRS UMR 6285 * Equipe DECIDE * UNIVERSITÉ DE BRETAGNE-SUD * Institut Universitaire de Technologie de Vannes * 8, Rue Montaigne - BP 561 56017 Vannes Cedex * * OrbisGIS is distributed under GPL 3 license. * * Copyright (C) 2007-2014 CNRS (IRSTV FR CNRS 2488) * Copyright (C) 2015-2017 CNRS (Lab-STICC UMR CNRS 6285) * * This file is part of OrbisGIS. * * OrbisGIS is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * OrbisGIS is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * OrbisGIS. If not, see <http://www.gnu.org/licenses/>. * * For more information, please consult: <http://www.orbisgis.org/> * or contact directly: * info_at_ orbisgis.org */ package org.orbisgis.tablegui.impl; import java.beans.EventHandler; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.*; import java.util.concurrent.ExecutorService; import javax.sql.DataSource; import javax.swing.RowSorter; import javax.swing.SortOrder; import org.orbisgis.corejdbc.common.IntegerUnion; import org.orbisgis.tablegui.impl.jobs.SortJob; import org.orbisgis.tablegui.impl.jobs.SortJobEventSorted; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class extends the swing RowSorter to launch SortJob * and to filter the shown rows. Currently the columns cannot be filtered. * @author Nicolas Fortin */ public class DataSourceRowSorter extends RowSorter<DataSourceTableModel> { private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceRowSorter.class); private DataSourceTableModel model; //If the Model rows do not reflect the DataSource row number //this array give the link between the TableModel Row Id //and the DataSource row ID private List<Integer> viewToModel = null; //The model can be filtered, then a model row can not be in the view private Map<Integer,Integer> modelToView = null; //Sorted columns private List<SortKey> sortedColumns = new ArrayList<>(); private DataSource dataSource; // Sort result given through JDBC private Collection<Integer> viewToModelJDBC; private ExecutorService executorService = null; /** * Constructor * @param model Datasource table model * @param dataSource JDBC Datasource */ public DataSourceRowSorter(DataSourceTableModel model, DataSource dataSource) { this.model = model; this.dataSource = dataSource; } /** * Push sort process in background thread using this executor service * @param executorService Instance of executor service or null */ public void setExecutorService(ExecutorService executorService) { this.executorService = executorService; } @Override public DataSourceTableModel getModel() { return model; } private void applyJDBCSort(int[] oldViewToModel) { Set<Integer> filter = null; if(viewToModel != null) { filter = new HashSet<>(viewToModel); } // Sorted is done using JDBC Index // And it is not filtered if(viewToModelJDBC != null) { viewToModel = new ArrayList<>(viewToModelJDBC.size()); for (int i : viewToModelJDBC) { if (filter == null) { viewToModel.add(i - 1); } else if (filter.contains(i - 1)) { viewToModel.add(i - 1); } } } initModelToView(); fireSortOrderChanged(); fireRowSorterChanged(oldViewToModel); } /** * Called by the Sort job listener * Update the internal indexes and inform the table. * @param sortData Sort result */ public void onRowSortDone(SortJobEventSorted sortData) { int[] oldViewToModel = getViewToModelArray(); viewToModelJDBC = sortData.getViewToModelIndex(); sortedColumns.clear(); sortedColumns.add(sortData.getSortRequest()); applyJDBCSort(oldViewToModel); } /** * Create the model to view from viewToModel */ private void initModelToView() { modelToView = new HashMap<>(); if(viewToModel != null) { for (int viewIndex = 0; viewIndex < viewToModel.size(); viewIndex++) { Integer modelIndex = viewToModel.get(viewIndex); modelToView.put(modelIndex, viewIndex); } } } private int[] getViewToModelArray() { int[] viewToModelArray = null; if(viewToModel!=null) { viewToModelArray = new int[viewToModel.size()]; for(int i=0;i<viewToModelArray.length;i++) { viewToModelArray[i]=viewToModel.get(i); } } return viewToModelArray; } @Override public void toggleSortOrder(int column) { if(isSortable(column)) { SortKey sortRequest=new SortKey(column, SortOrder.ASCENDING); boolean doReverse = true; //Find if the user already set an order for (SortKey col : sortedColumns) { if (col.getColumn() == column) { SortOrder order; if (col.getSortOrder().equals(SortOrder.ASCENDING)) { order = SortOrder.DESCENDING; } else { order = SortOrder.ASCENDING; } sortRequest = new SortKey(column, order); doReverse = false; break; } } //Multiple order is not available //To enable it, a new TableHeaderRenderer need to be defined //UIManager.getIcon("Table.ascendingSortIcon"); //UIManager.getIcon("Table.descendingSortIcon"); //http://www.jroller.com/nweber/entry/multi_column_sorting_w_mustang if(doReverse || viewToModelJDBC == null) { launchSortProcess(sortRequest); } else { // The user reverse the already sorted column int[] oldViewToModel = getViewToModelArray(); ArrayList<Integer> reversed = new ArrayList<>(viewToModelJDBC); Collections.reverse(reversed); viewToModelJDBC = reversed; sortedColumns.clear(); sortedColumns.add(sortRequest); applyJDBCSort(oldViewToModel); } } } private void launchSortProcess(SortKey sortInformation) { if(model.getRowCount() > 0) { SortJob sortJob = new SortJob(sortInformation, model, viewToModel, dataSource); sortJob.getEventSortedListeners().addListener(this, EventHandler.create(SortJob.SortJobListener.class, this, "onRowSortDone", "")); if(executorService != null) { executorService.execute(sortJob); } else { sortJob.execute(); } } } @Override public int convertRowIndexToModel(int index) { if(viewToModel==null) { return index; } else { return viewToModel.get(index); } } @Override public int convertRowIndexToView(int index) { if(modelToView==null) { return index; } Integer viewIndex = modelToView.get(index); if(viewIndex==null) { return -1; } else { return viewIndex; } } @Override public void setSortKeys(List<? extends SortKey> list) { if (list == null || list.isEmpty()) { setSortKey(null); } else { setSortKey(list.get(0)); } } /** * Sort the column in the provided order * * @param sortRequest The key to sort */ public void setSortKey(SortKey sortRequest) { if (sortRequest != null) { //Check if the sort request is not on the geometry column int sortIndex = sortRequest.getColumn(); if(!isSortable(sortIndex)) { //Ignore sort request return; } launchSortProcess(sortRequest); } else { sortedColumns.clear(); if(isFiltered()) { IntegerUnion shownRows = new IntegerUnion(viewToModel); clearIndex(); setRowsFilter(shownRows); } else { clearIndex(); } } } private void clearIndex() { if(viewToModel!=null) { int[] oldViewToModel = getViewToModelArray(); viewToModel = null; modelToView = null; fireSortOrderChanged(); fireRowSorterChanged(oldViewToModel); } } /** * Show only the provided model row id * * @param rowsFilter Rows to show, must be already visible in the view * row list (sort is not launch) */ public void setRowsFilter(IntegerUnion rowsFilter) { int[] oldViewToModel = getViewToModelArray(); if(rowsFilter!=null) { //Update the internal list viewToModel = new ArrayList<>(rowsFilter); initModelToView(); } else { viewToModel = null; modelToView = null; } // Apply sort on filtered result applyJDBCSort(oldViewToModel); } /** * * @return True if the shown rows are filtered */ public boolean isFiltered() { return getModelRowCount()!=getViewRowCount(); } private boolean isSortable(int columnIndex) { try { ResultSetMetaData meta = model.getRowSet().getMetaData(); return !meta.getColumnTypeName(columnIndex + 1).equalsIgnoreCase("geometry"); } catch (SQLException ex) { LOGGER.error(ex.getLocalizedMessage(), ex); return false; } } @Override public List<? extends SortKey> getSortKeys() { return sortedColumns; } @Override public int getViewRowCount() { if(viewToModel==null) { return getModelRowCount(); } return viewToModel.size(); } @Override public int getModelRowCount() { return model.getRowCount(); } /** * Launch sort processing and remove the row filter */ private void refreshSorter() { if(sortedColumns!=null && !sortedColumns.isEmpty()) { launchSortProcess(sortedColumns.get(0)); } } @Override public void modelStructureChanged() { refreshSorter(); } @Override public void allRowsChanged() { refreshSorter(); } @Override public void rowsInserted(int i, int i1) { clearIndex(); refreshSorter(); } @Override public void rowsDeleted(int i, int i1) { clearIndex(); refreshSorter(); } @Override public void rowsUpdated(int i, int i1) { refreshSorter(); } @Override public void rowsUpdated(int i, int i1, int i2) { refreshSorter(); } /** * The list of index correspondance between the seens rows and the model rows * @return The list or null if there is no sort or filter */ public List<Integer> getViewToModelIndex() { return Collections.unmodifiableList(viewToModel); } }