package com.revolsys.swing.table.record.model; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; import java.util.function.Predicate; import javax.annotation.PreDestroy; import javax.swing.SortOrder; import javax.swing.event.CellEditorListener; import javax.swing.event.ChangeEvent; import com.revolsys.datatype.DataType; import com.revolsys.datatype.DataTypes; import com.revolsys.geometry.model.Geometry; import com.revolsys.identifier.Identifier; import com.revolsys.record.Record; import com.revolsys.record.RecordState; import com.revolsys.record.code.CodeTable; import com.revolsys.record.schema.FieldDefinition; import com.revolsys.record.schema.RecordDefinition; import com.revolsys.swing.action.RunnableAction; import com.revolsys.swing.map.layer.record.LayerRecordMenu; import com.revolsys.swing.menu.MenuFactory; import com.revolsys.swing.table.SortableTableModel; import com.revolsys.swing.table.record.RecordRowTable; import com.revolsys.util.Dates; import com.revolsys.util.Property; import com.revolsys.util.Strings; public abstract class RecordRowTableModel extends AbstractRecordTableModel implements SortableTableModel, CellEditorListener { public static final String LOADING_VALUE = "\u2026"; private static final long serialVersionUID = 1L; private List<String> fieldNames = new ArrayList<>(); /** The columnIndex that the fields start. Allows for extra columns in subclasses.*/ private int fieldsOffset; private final List<String> fieldTitles = new ArrayList<>(); private Map<Integer, SortOrder> sortedColumns = new LinkedHashMap<>(); public RecordRowTableModel(final RecordDefinition recordDefinition) { this(recordDefinition, Collections.<String> emptyList()); } public RecordRowTableModel(final RecordDefinition recordDefinition, final Collection<String> fieldNames) { this(recordDefinition, fieldNames, 0); } public RecordRowTableModel(final RecordDefinition recordDefinition, final Collection<String> fieldNames, final int fieldsOffset) { super(recordDefinition); if (Property.hasValue(fieldNames)) { setFieldNamesAndTitles(fieldNames, Collections.<String> emptyList()); } this.fieldsOffset = fieldsOffset; if (recordDefinition != null) { final String idFieldName = recordDefinition.getIdFieldName(); setSortOrder(idFieldName); } } public <V extends Record> RunnableAction addMenuItem(final String groupName, final CharSequence name, final String iconName, final Consumer<V> consumer) { return addMenuItem(groupName, name, iconName, (Predicate<V>)null, consumer); } public <V extends Record> RunnableAction addMenuItem(final String groupName, final CharSequence name, final String iconName, final Predicate<V> enabledFilter, final Consumer<V> consumer) { final MenuFactory menu = getMenu(); return LayerRecordMenu.addMenuItem(menu, groupName, -1, name, null, iconName, enabledFilter, consumer); } public <V extends Record> RunnableAction addMenuItem(final String groupName, final int index, final CharSequence name, final String toolTip, final String iconName, final Predicate<V> enabledFilter, final Consumer<V> consumer) { final MenuFactory menu = getMenu(); return LayerRecordMenu.addMenuItem(menu, groupName, index, name, toolTip, iconName, enabledFilter, consumer); } public void clearSortedColumns() { synchronized (this.sortedColumns) { this.sortedColumns.clear(); fireTableDataChanged(); } } @Override @PreDestroy public void dispose() { super.dispose(); this.sortedColumns = null; } @Override public void editingCanceled(final ChangeEvent event) { } @Override public void editingStopped(final ChangeEvent event) { } @Override public Class<?> getColumnClass(final int columnIndex) { final FieldDefinition fieldDefinition = getColumnFieldDefinition(columnIndex); if (fieldDefinition == null) { return Object.class; } else { return fieldDefinition.getTypeClass(); } } @Override public int getColumnCount() { final int numColumns = this.fieldsOffset + this.fieldNames.size(); return numColumns; } public FieldDefinition getColumnFieldDefinition(final int columnIndex) { if (columnIndex < this.fieldsOffset) { return null; } else { final String fieldName = getColumnFieldName(columnIndex); final RecordDefinition recordDefinition = getRecordDefinition(); return recordDefinition.getField(fieldName); } } public int getColumnFieldIndex(final String fieldName) { return this.fieldNames.indexOf(fieldName) + this.fieldsOffset; } @Override public String getColumnFieldName(final int columnIndex) { if (columnIndex < this.fieldsOffset) { return null; } else { final int fieldIndex = columnIndex - this.fieldsOffset; if (fieldIndex < this.fieldNames.size()) { final String fieldName = this.fieldNames.get(fieldIndex); if (fieldName == null) { return null; } else { // TODO pre-calculate if (getRecordDefinition().hasField(fieldName)) { return fieldName; } else { final int index = fieldName.indexOf('.'); if (index == -1) { return fieldName; } else { return fieldName.substring(0, index); } } } } else { return null; } } } @Override public String getColumnFieldName(final int rowIndex, final int columnIndex) { return getColumnFieldName(columnIndex); } public int getColumnFieldsOffset() { return this.fieldsOffset; } public List<String> getColumnFieldTitles() { return this.fieldTitles; } @Override public String getColumnName(final int columnIndex) { if (columnIndex >= this.fieldsOffset) { final int fieldIndex = columnIndex - this.fieldsOffset; if (fieldIndex < this.fieldTitles.size()) { return this.fieldTitles.get(fieldIndex); } } return null; } @Override public Object getPrototypeValue(final int columnIndex) { FieldDefinition fieldDefinition = getColumnFieldDefinition(columnIndex); if (fieldDefinition == null) { return null; } else { final CodeTable codeTable = fieldDefinition.getCodeTable(); if (codeTable != null) { final FieldDefinition valueFieldDefinition = codeTable.getValueFieldDefinition(); if (valueFieldDefinition != null) { fieldDefinition = valueFieldDefinition; } else { Object maxValue = ""; int maxLength = 0; for (final Object value : codeTable.getValues()) { if (value != null) { final int length = DataTypes.toString(value).length(); if (length > maxLength) { maxValue = value; maxLength = length; } } } return maxValue; } } final DataType fieldType = fieldDefinition.getDataType(); final Class<?> fieldClass = fieldDefinition.getTypeClass(); final int fieldLength = fieldDefinition.getLength(); if (DataTypes.BOOLEAN.equals(fieldType)) { return Byte.MIN_VALUE; } else if (DataTypes.BYTE.equals(fieldType)) { return Byte.MIN_VALUE; } else if (DataTypes.SHORT.equals(fieldType)) { return Short.MAX_VALUE; } else if (DataTypes.INT.equals(fieldType)) { return Integer.MAX_VALUE; } else if (DataTypes.LONG.equals(fieldType)) { return Long.MAX_VALUE; } else if (DataTypes.SQL_DATE.equals(fieldType)) { return Dates.getSqlDate("9999-12-31"); } else if (DataTypes.DATE.equals(fieldType)) { return Dates.getDate("9999-12-29 23:59:59.999"); } else if (DataTypes.DATE_TIME.equals(fieldType)) { return Dates.getTimestamp("9999-12-29 23:59:59.999"); } else if (DataTypes.TIMESTAMP.equals(fieldType)) { return Dates.getTimestamp("9999-12-29 23:59:59.999"); } else if (Geometry.class.isAssignableFrom(fieldClass)) { return fieldType.getName(); } else { if (fieldLength < 1 || fieldLength > 30) { return "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; } else { final StringBuilder string = new StringBuilder(); for (int i = 0; i < fieldLength; i++) { string.append("X"); } return string.toString(); } } // if (Number.class.isAssignableFrom(fieldClass)) { // final StringBuilder numberString = new StringBuilder(); // final Object maxValue = fieldDefinition.getMaxValue(); // final Object minValue = fieldDefinition.getMinValue(); // if (maxValue == null) { // for (int i = 0; i <= maxLength; i++) { // numberString.append("9"); // } // return fieldType.toObject(numberString.toString()); // } else { // if (minValue instanceof Number) { // numberString.append(((Number)minValue).longValue()); // } // } // if (Float.class.isAssignableFrom(fieldClass) || // Double.class.isAssignableFrom(fieldClass) // || BigDecimal.class.isAssignableFrom(fieldClass)) { // numberString.append('.'); // final int fieldScale = fieldDefinition.getScale(); // for (int i = 0; i <= fieldScale; i++) { // numberString.append("9"); // } // final BigDecimal scaleValue = new BigDecimal(numberString.toString()); // } // } // return super.getPrototypeValue(columnIndex); } } public abstract <V extends Record> V getRecord(final int row); public <V extends Record> List<V> getRecords(final int[] rows) { final List<V> records = new ArrayList<>(); for (final int row : rows) { final V record = getRecord(row); if (record != null) { records.add(record); } } return records; } public Map<Integer, SortOrder> getSortedColumns() { return this.sortedColumns; } @Override public SortOrder getSortOrder(final int columnIndex) { synchronized (this.sortedColumns) { return this.sortedColumns.get(columnIndex); } } @Override public RecordRowTable getTable() { return (RecordRowTable)super.getTable(); } @Override public Object getValueAt(final int rowIndex, final int columnIndex) { if (columnIndex < this.fieldsOffset) { return null; } else { final Record record = getRecord(rowIndex); if (record == null) { return LOADING_VALUE; } else if (record.getState() == RecordState.INITIALIZING) { return LOADING_VALUE; } else { final String name = getColumnFieldName(columnIndex); return getRecordValue(record, name); } } } protected Object getRecordValue(final Record record, final String name) { final Object value = record.getValue(name); return value; } @Override public boolean isCellEditable(final int rowIndex, final int columnIndex) { if (isEditable()) { final Record record = getRecord(rowIndex); if (record != null) { return isCellEditable(rowIndex, columnIndex, record); } } return false; } protected boolean isCellEditable(final int rowIndex, final int columnIndex, final Record record) { final RecordState state = record.getState(); if (state != RecordState.INITIALIZING && state != RecordState.DELETED) { final String fieldName = getColumnFieldName(rowIndex, columnIndex); if (fieldName != null) { if (!isReadOnly(fieldName)) { final RecordDefinition recordDefinition = getRecordDefinition(); final Class<?> fieldClass = recordDefinition.getFieldClass(fieldName); if (!Geometry.class.isAssignableFrom(fieldClass)) { return true; } } } } return false; } public boolean isFieldEditable(final int columnIndex) { final String fieldName = getColumnFieldName(columnIndex); if (fieldName != null) { if (!isReadOnly(fieldName)) { final RecordDefinition recordDefinition = getRecordDefinition(); final Class<?> fieldClass = recordDefinition.getFieldClass(fieldName); if (!Geometry.class.isAssignableFrom(fieldClass)) { return true; } } } return false; } public boolean isIdField(final int columnIndex) { final String fieldName = getColumnFieldName(columnIndex); if (fieldName != null) { final RecordDefinition recordDefinition = getRecordDefinition(); return recordDefinition.getIdFieldNames().contains(fieldName); } return false; } @Override public boolean isSelected(boolean selected, final int rowIndex, final int columnIndex) { final RecordRowTable table = getTable(); final int[] selectedRows = table.getSelectedRows(); selected = false; for (final int selectedRow : selectedRows) { if (rowIndex == selectedRow) { return true; } } return false; } public boolean isSorted(final int columnIndex) { synchronized (this.sortedColumns) { return this.sortedColumns.containsKey(columnIndex); } } public void setFieldNames(final Collection<String> fieldNames) { if (fieldNames == null || fieldNames.isEmpty()) { final RecordDefinition recordDefinition = getRecordDefinition(); this.fieldNames = new ArrayList<>(recordDefinition.getFieldNames()); } else { this.fieldNames = new ArrayList<>(fieldNames); } fireTableStructureChanged(); } public void setFieldNamesAndTitles(final Collection<String> fieldNames, final List<String> fieldTitles) { if (fieldNames == null || fieldNames.isEmpty()) { final RecordDefinition recordDefinition = getRecordDefinition(); this.fieldNames = new ArrayList<>(recordDefinition.getFieldNames()); } else { this.fieldNames = new ArrayList<>(fieldNames); } this.fieldTitles.clear(); for (int i = 0; i < this.fieldNames.size(); i++) { String title; if (i < fieldTitles.size()) { title = fieldTitles.get(i); } else { final String fieldName = getColumnFieldName(i); final RecordDefinition recordDefinition = getRecordDefinition(); final FieldDefinition fieldDefinition = recordDefinition.getField(fieldName); title = fieldDefinition.getTitle(); } this.fieldTitles.add(title); } fireTableStructureChanged(); } public void setFieldsOffset(final int fieldsOffset) { this.fieldsOffset = fieldsOffset; } public void setFieldTitles(final List<String> fieldTitles) { this.fieldTitles.clear(); for (int i = 0; i < this.fieldNames.size(); i++) { String title; if (i < fieldTitles.size()) { title = fieldTitles.get(i); } else { final String fieldName = getColumnFieldName(i); final RecordDefinition recordDefinition = getRecordDefinition(); final FieldDefinition fieldDefinition = recordDefinition.getField(fieldName); title = fieldDefinition.getTitle(); } this.fieldTitles.add(title); } fireTableStructureChanged(); } protected void setRecordValue(final Record record, final String fieldName, final Object value) { final Object objectValue = toObjectValue(fieldName, value); record.setValue(fieldName, objectValue); } public void setSortedColumns(final Map<Integer, SortOrder> sortedColumns) { this.sortedColumns = new LinkedHashMap<>(); if (sortedColumns != null) { this.sortedColumns.putAll(sortedColumns); } } @Override public SortOrder setSortOrder(final int columnIndex) { synchronized (this.sortedColumns) { SortOrder sortOrder = this.sortedColumns.get(columnIndex); this.sortedColumns.clear(); if (sortOrder == SortOrder.ASCENDING) { sortOrder = SortOrder.DESCENDING; } else { sortOrder = SortOrder.ASCENDING; } this.sortedColumns.put(columnIndex, sortOrder); fireTableDataChanged(); return sortOrder; } } public SortOrder setSortOrder(final int columnIndex, final SortOrder sortOrder) { synchronized (this.sortedColumns) { this.sortedColumns.clear(); this.sortedColumns.put(columnIndex, sortOrder); fireTableDataChanged(); return sortOrder; } } // TODO initial sort order for session layers doesn't always work public SortOrder setSortOrder(final String fieldName) { int index = 0; if (Property.hasValue(fieldName)) { index = this.fieldNames.indexOf(fieldName); if (index == -1) { index = 0; } } final FieldDefinition fieldDefinition = getColumnFieldDefinition(index); if (fieldDefinition != null) { final Class<?> fieldClass = fieldDefinition.getTypeClass(); if (Geometry.class.isAssignableFrom(fieldClass)) { return SortOrder.ASCENDING; } } return setSortOrder(index + this.fieldsOffset, SortOrder.ASCENDING); } @Override public void setValueAt(final Object value, final int rowIndex, final int columnIndex) { if (isCellEditable(rowIndex, columnIndex)) { if (columnIndex >= this.fieldsOffset) { final Record record = getRecord(rowIndex); if (record != null) { final String fieldName = getColumnFieldName(columnIndex); setRecordValue(record, fieldName, value); } } } } @Override public String toCopyValue(final int rowIndex, int fieldIndex, final Object recordValue) { if (fieldIndex < this.fieldsOffset) { return DataTypes.toString(recordValue); } else { fieldIndex -= this.fieldsOffset; String text; final RecordDefinition recordDefinition = getRecordDefinition(); final String idFieldName = recordDefinition.getIdFieldName(); final String name = getColumnFieldName(fieldIndex); if (recordValue == null) { return null; } else { if (recordValue instanceof Geometry) { final Geometry geometry = (Geometry)recordValue; return geometry.toString(); } CodeTable codeTable = null; if (!name.equals(idFieldName)) { codeTable = recordDefinition.getCodeTableByFieldName(name); } if (codeTable == null) { text = DataTypes.toString(recordValue); } else { final List<Object> values = codeTable.getValues(Identifier.newIdentifier(recordValue)); if (values == null || values.isEmpty()) { return null; } else { text = Strings.toString(values); } } if (text.length() == 0) { return null; } } return text; } } @Override public final String toDisplayValue(final int rowIndex, final int fieldIndex, final Object objectValue) { final Record record = getRecord(rowIndex); if (record == null) { return null; } else { if (record.getState() == RecordState.INITIALIZING) { return LOADING_VALUE; } else { return toDisplayValueInternal(rowIndex, fieldIndex, objectValue); } } } protected String toDisplayValueInternal(final int rowIndex, final int fieldIndex, final Object objectValue) { return super.toDisplayValue(rowIndex, fieldIndex, objectValue); } }