package com.revolsys.swing.map.layer.record.table.model;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Consumer;
import javax.swing.JMenuItem;
import javax.swing.ListSelectionModel;
import javax.swing.RowFilter;
import javax.swing.SortOrder;
import com.revolsys.beans.PropertyChangeSupportProxy;
import com.revolsys.collection.CollectionUtil;
import com.revolsys.collection.list.Lists;
import com.revolsys.datatype.DataType;
import com.revolsys.geometry.model.BoundingBox;
import com.revolsys.io.BaseCloseable;
import com.revolsys.record.Record;
import com.revolsys.record.Records;
import com.revolsys.record.query.Condition;
import com.revolsys.record.query.Query;
import com.revolsys.record.query.functions.F;
import com.revolsys.record.schema.FieldDefinition;
import com.revolsys.record.schema.RecordDefinition;
import com.revolsys.swing.EventQueue;
import com.revolsys.swing.action.RunnableAction;
import com.revolsys.swing.listener.EventQueueRunnableListener;
import com.revolsys.swing.map.layer.Project;
import com.revolsys.swing.map.layer.record.AbstractRecordLayer;
import com.revolsys.swing.map.layer.record.LayerRecord;
import com.revolsys.swing.map.layer.record.LayerRecordMenu;
import com.revolsys.swing.map.layer.record.table.RecordLayerTable;
import com.revolsys.swing.menu.BaseJPopupMenu;
import com.revolsys.swing.parallel.Invoke;
import com.revolsys.swing.table.BaseJTable;
import com.revolsys.swing.table.SortableTableModel;
import com.revolsys.swing.table.record.filter.RecordRowPredicateRowFilter;
import com.revolsys.swing.table.record.model.RecordRowTableModel;
import com.revolsys.util.Property;
public class RecordLayerTableModel extends RecordRowTableModel
implements SortableTableModel, PropertyChangeSupportProxy {
private static final ModeEmpty MODE_EMPTY = new ModeEmpty();
public static final String MODE_RECORDS_ALL = "all";
public static final String MODE_RECORDS_CHANGED = "edits";
public static final String MODE_RECORDS_SELECTED = "selected";
private static final long serialVersionUID = 1L;
public static RecordLayerTable newTable(final AbstractRecordLayer layer) {
return newTable(layer, layer.getFieldNamesSet());
}
public static RecordLayerTable newTable(final AbstractRecordLayer layer,
final Collection<String> fieldNames) {
final RecordDefinition recordDefinition = layer.getRecordDefinition();
if (recordDefinition == null) {
return null;
} else {
final RecordLayerTableModel model = new RecordLayerTableModel(layer, fieldNames);
final RecordLayerTable table = new RecordLayerTable(model);
model.selectionChangedListener = EventQueue.addPropertyChange(layer, "hasSelectedRecords",
() -> selectionChanged(table, model));
return table;
}
}
public static RecordLayerTable newTable(final AbstractRecordLayer layer,
final String... fieldNames) {
return newTable(layer, Arrays.asList(fieldNames));
}
public static final void selectionChanged(final RecordLayerTable table,
final RecordLayerTableModel tableModel) {
table.repaint();
}
private TableRecordsMode tableRecordsMode;
private final Map<String, TableRecordsMode> tableRecordsModeByKey = new LinkedHashMap<>();
private Condition filter = Condition.ALL;
private boolean filterByBoundingBox;
private final LinkedList<Condition> filterHistory = new LinkedList<>();
private final AbstractRecordLayer layer;
private Map<String, Boolean> orderBy;
private Comparator<Record> orderByComparatorIdentifier = null;
private RowFilter<RecordRowTableModel, Integer> rowFilterCondition = null;
private EventQueueRunnableListener selectionChangedListener;
private final Object sync = new Object();
private boolean useRecordMenu = true;
public RecordLayerTableModel(final AbstractRecordLayer layer,
final Collection<String> fieldNames) {
super(layer.getRecordDefinition());
this.layer = layer;
setFieldNames(fieldNames);
setEditable(true);
setReadOnlyFieldNames(layer.getUserReadOnlyFieldNames());
final String idFieldName = getRecordDefinition().getIdFieldName();
setSortOrder(idFieldName);
addFieldFilterMode(new ModeAllPaged(this));
addFieldFilterMode(new ModeChanged(this));
addFieldFilterMode(new ModeSelected(this));
}
protected void addFieldFilterMode(final TableRecordsMode tableRecordsMode) {
final String key = tableRecordsMode.getKey();
this.tableRecordsModeByKey.put(key, tableRecordsMode);
}
@Override
public void dispose() {
getTable().setSelectionModel(null);
Property.removeListener(this.layer, "hasSelectedRecords", this.selectionChangedListener);
final TableRecordsMode tableRecordsMode = getTableRecordsMode();
if (tableRecordsMode != null) {
tableRecordsMode.deactivate();
}
this.selectionChangedListener = null;
super.dispose();
}
public void exportRecords(final Object target) {
final TableRecordsMode tableRecordsMode = getTableRecordsMode();
if (tableRecordsMode != null) {
final Query query = getFilterQuery();
tableRecordsMode.exportRecords(query, target);
}
}
public void forEachRecord(final Consumer<? super LayerRecord> action) {
final TableRecordsMode tableRecordsMode = getTableRecordsMode();
if (tableRecordsMode != null) {
final Query query = getFilterQuery();
try (
BaseCloseable eventsDisabled = this.layer.eventsDisabled()) {
tableRecordsMode.forEachRecord(query, action);
}
refresh();
}
}
public List<TableRecordsMode> getFieldFilterModes() {
return Lists.toArray(this.tableRecordsModeByKey.values());
}
public Condition getFilter() {
return this.filter;
}
public LinkedList<Condition> getFilterHistory() {
return this.filterHistory;
}
protected Query getFilterQuery() {
final Query query = this.layer.getQuery();
final Condition filter = getFilter();
query.and(filter);
query.setOrderBy(this.orderBy);
if (this.filterByBoundingBox) {
final Project project = this.layer.getProject();
final BoundingBox viewBoundingBox = project.getViewBoundingBox();
final RecordDefinition recordDefinition = this.layer.getRecordDefinition();
final FieldDefinition geometryField = recordDefinition.getGeometryField();
if (geometryField != null) {
query.and(F.envelopeIntersects(geometryField, viewBoundingBox));
}
}
return query;
}
public String getGeometryFilterMode() {
if (this.filterByBoundingBox) {
return "boundingBox";
}
return "all";
}
public AbstractRecordLayer getLayer() {
return this.layer;
}
@Override
public BaseJPopupMenu getMenu(final int rowIndex, final int columnIndex) {
final LayerRecord record = getRecord(rowIndex);
if (record != null) {
final AbstractRecordLayer layer = getLayer();
if (layer != null) {
LayerRecordMenu.setEventRecord(record);
if (isUseRecordMenu()) {
final LayerRecordMenu menu = record.getMenu();
final BaseJPopupMenu popupMenu = menu.newJPopupMenu();
popupMenu.addSeparator();
final RecordLayerTable table = getTable();
final boolean cellEditable = isCellEditable(rowIndex, columnIndex);
final Object value = getValueAt(rowIndex, columnIndex);
final boolean canCopy = Property.hasValue(value);
if (cellEditable) {
final JMenuItem cutMenu = RunnableAction.newMenuItem("Cut Field Value", "cut",
table::cutFieldValue);
cutMenu.setEnabled(canCopy);
popupMenu.add(cutMenu);
}
final JMenuItem copyMenu = RunnableAction.newMenuItem("Copy Field Value", "page_copy",
table::copyFieldValue);
copyMenu.setEnabled(canCopy);
popupMenu.add(copyMenu);
if (cellEditable) {
popupMenu.add(RunnableAction.newMenuItem("Paste Field Value", "paste_plain",
table::pasteFieldValue));
}
return popupMenu;
} else {
return super.getMenu().newJPopupMenu();
}
}
}
return null;
}
public Map<String, Boolean> getOrderBy() {
return this.orderBy;
}
public Comparator<Record> getOrderByComparatorIdentifier() {
return this.orderByComparatorIdentifier;
}
@SuppressWarnings("unchecked")
@Override
public <V extends Record> V getRecord(final int rowIndex) {
LayerRecord record = null;
if (rowIndex >= 0) {
final TableRecordsMode tableRecordsMode = getTableRecordsMode();
if (tableRecordsMode != null) {
record = tableRecordsMode.getRecord(rowIndex);
}
}
return (V)record;
}
@Override
public final int getRowCount() {
final TableRecordsMode tableRecordsMode = getTableRecordsMode();
if (tableRecordsMode == null) {
return 0;
} else {
return tableRecordsMode.getRecordCount();
}
}
public RowFilter<RecordRowTableModel, Integer> getRowFilter() {
if (isSortable()) {
return this.rowFilterCondition;
} else {
return null;
}
}
public ListSelectionModel getSelectionModel() {
final TableRecordsMode tableRecordsMode = getTableRecordsMode();
if (tableRecordsMode == null) {
return null;
} else {
return tableRecordsMode.getSelectionModel();
}
}
@Override
public RecordLayerTable getTable() {
return (RecordLayerTable)super.getTable();
}
public TableRecordsMode getTableRecordsMode() {
if (this.tableRecordsMode == null || !this.tableRecordsMode.isEnabled()) {
setTableRecordsMode(CollectionUtil.get(this.tableRecordsModeByKey.values(), 0));
}
return this.tableRecordsMode;
}
public TableRecordsMode getTableRecordsMode(final String key) {
return this.tableRecordsModeByKey.get(key);
}
public String getTypeName() {
return getRecordDefinition().getPath();
}
@Override
protected boolean isCellEditable(final int rowIndex, final int columnIndex, final Record record) {
final AbstractRecordLayer layer = getLayer();
final LayerRecord layerRecord = (LayerRecord)record;
if (layer.isDeleted(layerRecord)) {
return false;
} else if (layer.isCanEditRecords() || layer.isNew(layerRecord) && layer.isCanAddRecords()) {
return super.isCellEditable(rowIndex, columnIndex, record);
} else {
return false;
}
}
public boolean isDeleted(final int rowIndex) {
final LayerRecord record = getRecord(rowIndex);
if (record != null) {
final AbstractRecordLayer layer = getLayer();
if (layer != null) {
return layer.isDeleted(record);
}
}
return false;
}
@Override
public boolean isEditable() {
return super.isEditable() && this.layer.isEditable() && this.layer.isCanEditRecords();
}
@Override
public boolean isFieldEditable(final int columnIndex) {
if (this.layer.isEditable()) {
return super.isFieldEditable(columnIndex);
} else {
return false;
}
}
public boolean isFilterByBoundingBox() {
return this.filterByBoundingBox;
}
public boolean isFilterByBoundingBoxSupported() {
if (this.tableRecordsMode == null) {
return true;
}
return this.tableRecordsMode.isFilterByBoundingBoxSupported();
}
public boolean isHasFilter() {
return this.filter != null && !this.filter.isEmpty();
}
public boolean isHasFilterHistory() {
return !this.filterHistory.isEmpty();
}
@Override
public boolean isSelected(final boolean selected, final int rowIndex, final int columnIndex) {
final LayerRecord record = getRecord(rowIndex);
return this.layer.isSelected(record);
}
public boolean isSortable() {
final TableRecordsMode tableRecordsMode = getTableRecordsMode();
if (tableRecordsMode == null) {
return false;
} else {
return tableRecordsMode.isSortable();
}
}
public boolean isUseRecordMenu() {
return this.useRecordMenu;
}
public void refresh() {
final TableRecordsMode tableRecordsMode = getTableRecordsMode();
if (tableRecordsMode != null) {
tableRecordsMode.refresh();
}
}
protected void repaint() {
final RecordLayerTable table = getTable();
if (table != null) {
table.repaint();
}
}
@Override
public void setFieldNames(final Collection<String> fieldNames) {
final List<String> fieldTitles = new ArrayList<>();
for (final String fieldName : fieldNames) {
final String fieldTitle = this.layer.getFieldTitle(fieldName);
fieldTitles.add(fieldTitle);
}
super.setFieldNamesAndTitles(fieldNames, fieldTitles);
}
public void setFieldNames(final String... fieldNames) {
setFieldNames(Arrays.asList(fieldNames));
}
public void setFieldNamesSetName(final String fieldNamesSetName) {
this.layer.setFieldNamesSetName(fieldNamesSetName);
final List<String> fieldNamesSet = this.layer.getFieldNamesSet();
setFieldNames(fieldNamesSet);
}
public void setFilter(final Condition filter) {
Invoke.later(() -> {
final Condition filter2;
if (filter == null) {
filter2 = Condition.ALL;
} else {
filter2 = filter;
}
if (!DataType.equal(filter2, this.filter)) {
final Object oldValue = this.filter;
this.filter = filter2;
if (Property.isEmpty(filter2)) {
this.rowFilterCondition = null;
} else {
this.rowFilterCondition = new RecordRowPredicateRowFilter(filter2);
if (!DataType.equal(oldValue, filter2)) {
this.filterHistory.remove(filter2);
this.filterHistory.addFirst(filter2);
while (this.filterHistory.size() > 20) {
this.filterHistory.removeLast();
}
firePropertyChange("hasFilterHistory", false, true);
}
}
if (isSortable()) {
final RecordLayerTable table = getTable();
table.setRowFilter(this.rowFilterCondition);
} else {
refresh();
}
firePropertyChange("filter", oldValue, this.filter);
final boolean hasFilter = isHasFilter();
firePropertyChange("hasFilter", !hasFilter, hasFilter);
}
});
}
public void setFilterByBoundingBox(boolean filterByBoundingBox) {
final String geometryFilterMode = getGeometryFilterMode();
final String oldValue = geometryFilterMode;
if (!this.tableRecordsMode.isFilterByBoundingBoxSupported()) {
filterByBoundingBox = false;
}
if (this.filterByBoundingBox != filterByBoundingBox) {
this.filterByBoundingBox = filterByBoundingBox;
refresh();
}
firePropertyChange("geometryFilterMode", oldValue, geometryFilterMode);
}
public String setGeometryFilterMode(final String mode) {
final boolean filterByBoundingBox = "boundingBox".equals(mode);
setFilterByBoundingBox(filterByBoundingBox);
return getGeometryFilterMode();
}
public void setOrderBy(final Map<String, Boolean> orderBy) {
setOrderByInternal(orderBy);
final Map<Integer, SortOrder> sortedColumns = new LinkedHashMap<>();
for (final Entry<String, Boolean> entry : orderBy.entrySet()) {
if (orderBy != null) {
final String fieldName = entry.getKey();
final Boolean order = entry.getValue();
final int index = getColumnFieldIndex(fieldName);
if (index != -1) {
SortOrder sortOrder;
if (order) {
sortOrder = SortOrder.ASCENDING;
} else {
sortOrder = SortOrder.DESCENDING;
}
sortedColumns.put(index, sortOrder);
}
}
}
setSortedColumns(sortedColumns);
}
private void setOrderByInternal(final Map<String, Boolean> orderBy) {
if (Property.hasValue(orderBy)) {
this.orderBy = orderBy;
this.orderByComparatorIdentifier = Records.newComparatorOrderByIdentifier(orderBy);
} else {
this.orderBy = Collections.emptyMap();
this.orderByComparatorIdentifier = null;
}
}
@Override
public SortOrder setSortOrder(final int columnIndex) {
final SortOrder sortOrder = super.setSortOrder(columnIndex);
final String fieldName = getColumnFieldName(columnIndex);
if (Property.hasValue(fieldName)) {
Map<String, Boolean> orderBy;
if (sortOrder == SortOrder.ASCENDING) {
orderBy = Collections.singletonMap(fieldName, true);
} else if (sortOrder == SortOrder.DESCENDING) {
orderBy = Collections.singletonMap(fieldName, false);
} else {
orderBy = Collections.singletonMap(fieldName, true);
}
if (this.sync == null) {
setOrderByInternal(orderBy);
} else {
setOrderByInternal(orderBy);
refresh();
}
}
return sortOrder;
}
@Override
public SortOrder setSortOrder(final int columnIndex, final SortOrder sortOrder) {
super.setSortOrder(columnIndex, sortOrder);
final String fieldName = getColumnFieldName(columnIndex);
if (Property.hasValue(fieldName)) {
Map<String, Boolean> orderBy;
if (sortOrder == SortOrder.ASCENDING) {
orderBy = Collections.singletonMap(fieldName, true);
} else if (sortOrder == SortOrder.DESCENDING) {
orderBy = Collections.singletonMap(fieldName, false);
} else {
orderBy = Collections.singletonMap(fieldName, true);
}
if (this.sync == null) {
setOrderByInternal(orderBy);
} else {
setOrderByInternal(orderBy);
refresh();
}
}
return sortOrder;
}
@Override
public void setTable(final BaseJTable table) {
super.setTable(table);
final ListSelectionModel selectionModel = getSelectionModel();
table.setSelectionModel(selectionModel);
}
public void setTableRecordsMode(final TableRecordsMode tableRecordsMode) {
Invoke.later(() -> {
final TableRecordsMode oldMode = this.tableRecordsMode;
final RecordLayerTable table = getTable();
if (table != null && tableRecordsMode != null && tableRecordsMode != oldMode) {
if (oldMode != null) {
oldMode.deactivate();
}
final String oldGeometryFilterMode = getGeometryFilterMode();
this.tableRecordsMode = MODE_EMPTY;
fireTableDataChanged();
table.setSortable(false);
table.setSelectionModel(null);
table.setRowFilter(null);
tableRecordsMode.activate();
final ListSelectionModel selectionModel = tableRecordsMode.getSelectionModel();
table.setSelectionModel(selectionModel);
final boolean sortable = tableRecordsMode.isSortable();
table.setSortable(sortable);
final RowFilter<RecordRowTableModel, Integer> rowFilter = getRowFilter();
table.setRowFilter(rowFilter);
final boolean filterByBoundingBoxSupported = tableRecordsMode
.isFilterByBoundingBoxSupported();
if (!filterByBoundingBoxSupported) {
this.filterByBoundingBox = false;
}
this.tableRecordsMode = tableRecordsMode;
refresh();
firePropertyChange("tableRecordsMode", oldMode, this.tableRecordsMode);
firePropertyChange("geometryFilterMode", oldGeometryFilterMode, getGeometryFilterMode());
firePropertyChange("filterByBoundingBox", !this.filterByBoundingBox,
this.filterByBoundingBox);
firePropertyChange("filterByBoundingBoxSupported", !filterByBoundingBoxSupported,
filterByBoundingBoxSupported);
}
});
}
public void setUseRecordMenu(final boolean useRecordMenu) {
this.useRecordMenu = useRecordMenu;
}
@Override
public String toDisplayValueInternal(final int rowIndex, final int fieldIndex,
final Object objectValue) {
if (objectValue == null) {
final String fieldName = getColumnFieldName(fieldIndex);
final RecordDefinition recordDefinition = getRecordDefinition();
final List<String> idFieldNames = recordDefinition.getIdFieldNames();
if (idFieldNames.contains(fieldName)) {
return "NEW";
}
}
return super.toDisplayValueInternal(rowIndex, fieldIndex, objectValue);
}
}