package com.revolsys.swing.map.layer.record.table.model;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.function.Supplier;
import javax.swing.ListSelectionModel;
import com.revolsys.collection.list.ListByIndexIterator;
import com.revolsys.collection.list.Lists;
import com.revolsys.record.query.Condition;
import com.revolsys.record.query.Query;
import com.revolsys.swing.map.layer.record.AbstractRecordLayer;
import com.revolsys.swing.map.layer.record.LayerRecord;
import com.revolsys.swing.parallel.Invoke;
import com.revolsys.util.Property;
public abstract class ModeAbstractCached implements TableRecordsMode {
private final String key;
private final AtomicLong refreshIndex = new AtomicLong(Long.MIN_VALUE + 1);
private List<LayerRecord> records = new ArrayList<>();
private int recordCount;
private final RecordLayerTableModel model;
private final List<PropertyChangeListener> listeners = new ArrayList<>();
private long lastRefreshIndex = Long.MIN_VALUE;
private LayerRecord currentRecord;
private int currentRowIndex;
private ListSelectionModel selectionModel;
public ModeAbstractCached(final String key, final RecordLayerTableModel model) {
this.key = key;
this.model = model;
}
@Override
public void activate() {
this.selectionModel = newSelectionModel(this.model);
final AbstractRecordLayer layer = getLayer();
final PropertyChangeListener recordFieldListener = this::recordFieldChanged;
layer.addPropertyChangeListener(recordFieldListener);
addListeners( //
Property.addListenerNewValueSource(layer, AbstractRecordLayer.RECORD_UPDATED,
this::recordUpdated), //
recordFieldListener);
}
protected void addCachedRecord(final LayerRecord record) {
if (record != null) {
addCachedRecords(Collections.singletonList(record));
}
}
protected void addCachedRecords(final Iterable<? extends LayerRecord> records) {
if (records != null) {
Invoke.later(() -> {
final int fromIndex = this.records.size();
int addCount = 0;
for (final LayerRecord record : records) {
if (canAddCachedRecord(record)) {
final int index = record.addTo(this.records);
if (index != -1) {
addCount++;
}
}
}
if (addCount > 0) {
clearCurrentRecord();
setRecordCount(this.recordCount + addCount);
this.model.fireTableRowsInserted(fromIndex, fromIndex + addCount - 1);
}
});
}
}
protected void addListeners(final PropertyChangeListener... listeners) {
Lists.addAll(this.listeners, listeners);
}
protected boolean canAddCachedRecord(final LayerRecord record) {
return true;
}
protected boolean canRefreshFinish(final long index) {
if (index >= this.lastRefreshIndex) {
this.lastRefreshIndex = index;
return true;
} else {
return false;
}
}
protected void clearCurrentRecord() {
this.currentRecord = null;
this.currentRowIndex = -1;
}
@Override
public void deactivate() {
for (final Object source : Arrays.asList(this.model, getLayer())) {
for (final PropertyChangeListener listener : this.listeners) {
Property.removeListener(source, listener);
}
}
clearCurrentRecord();
this.listeners.clear();
this.recordCount = 0;
this.records = new ArrayList<>();
this.selectionModel = null;
}
@Override
public void exportRecords(final Query query, final Object target) {
final Condition filter = query.getWhereCondition();
final Map<String, Boolean> orderBy = query.getOrderBy();
final AbstractRecordLayer layer = getLayer();
final Iterable<LayerRecord> records = new ListByIndexIterator<>(this.records);
layer.exportRecords(records, filter, orderBy, target);
}
protected void fireRecordUpdated(final int index) {
clearCurrentRecord();
this.model.fireTableRowsUpdated(index, index);
}
protected void fireTableDataChanged() {
clearCurrentRecord();
this.model.fireTableDataChanged();
}
@Override
public void forEachRecord(final Query query, final Consumer<? super LayerRecord> action) {
final Condition filter = query.getWhereCondition();
final Map<String, Boolean> orderBy = query.getOrderBy();
final AbstractRecordLayer layer = getLayer();
final Iterable<LayerRecord> records = new ListByIndexIterator<>(this.records);
layer.forEachRecord(records, filter, orderBy, action);
}
protected Condition getFilter() {
return this.model.getFilter();
}
protected Query getFilterQuery() {
return this.model.getFilterQuery();
}
@Override
public String getKey() {
return this.key;
}
public AbstractRecordLayer getLayer() {
return this.model.getLayer();
}
@Override
public final LayerRecord getRecord(final int rowIndex) {
LayerRecord record = null;
if (rowIndex >= 0) {
if (rowIndex == this.currentRowIndex && this.currentRecord != null) {
record = this.currentRecord;
} else {
record = getRecordDo(rowIndex);
this.currentRecord = record;
this.currentRowIndex = rowIndex;
}
}
return record;
}
@Override
public int getRecordCount() {
return this.recordCount;
}
protected LayerRecord getRecordDo(final int index) {
final List<LayerRecord> records = this.records;
synchronized (records) {
if (index >= 0 && index < records.size()) {
try {
return records.get(index);
} catch (final ArrayIndexOutOfBoundsException e) {
return null;
}
} else {
return null;
}
}
}
protected List<LayerRecord> getRecordsForCache() {
return Collections.emptyList();
}
protected long getRefreshIndex() {
return this.refreshIndex.get();
}
protected long getRefreshIndexNext() {
return this.refreshIndex.incrementAndGet();
}
@Override
public final ListSelectionModel getSelectionModel() {
return this.selectionModel;
}
protected RecordLayerTableModel getTableModel() {
return this.model;
}
private int indexOf(final LayerRecord record) {
if (record == null) {
return -1;
} else {
return record.indexOf(this.records);
}
}
protected PropertyChangeListener newRecordsDeletedListener(final AbstractRecordLayer layer) {
return Property.<List<LayerRecord>> addListenerNewValueSource(layer,
AbstractRecordLayer.RECORDS_DELETED, this::recordsDeleted);
}
protected ListSelectionModel newSelectionModel(final RecordLayerTableModel tableModel) {
return new RecordLayerListSelectionModel(tableModel);
}
private void recordFieldChanged(final LayerRecord record, final String fieldName,
final Object value) {
final int rowIndex = indexOf(record);
if (rowIndex != -1) {
final RecordLayerTableModel model = getTableModel();
final int fieldIndex = model.getColumnFieldIndex(fieldName);
if (fieldIndex == -1) {
repaint();
} else {
model.fireTableCellUpdated(rowIndex, fieldIndex);
}
}
}
private void recordFieldChanged(final PropertyChangeEvent event) {
final Object source = event.getSource();
if (source instanceof LayerRecord) {
final String propertyName = event.getPropertyName();
final Object newValue = event.getNewValue();
recordFieldChanged((LayerRecord)source, propertyName, newValue);
}
}
protected void recordsDeleted(final List<LayerRecord> records) {
Invoke.later(() -> {
for (final LayerRecord record : records) {
if (record.getLayer().isDeleted(record)) {
removeCachedRecord(record);
}
}
repaint();
});
}
protected void recordUpdated(final LayerRecord record) {
repaint();
}
@Override
public void refresh() {
Invoke.later(() -> {
final long refreshIndex = getRefreshIndexNext();
clearCurrentRecord();
refresh(refreshIndex);
});
}
public void refresh(final long refreshIndex) {
final Supplier<List<LayerRecord>> backgroundTask = this::getRecordsForCache;
final Consumer<List<LayerRecord>> doneTask = (records) -> {
if (canRefreshFinish(refreshIndex)) {
this.recordCount = 0; // Set to 0 to avoid array index exceptions
this.records = records;
this.recordCount = records.size();
fireTableDataChanged();
}
};
Invoke.background("Refresh table records", backgroundTask, doneTask);
}
protected void removeCachedRecord(final LayerRecord record) {
Invoke.later(() -> {
final int index = record.removeFrom(this.records);
if (index != -1) {
clearCurrentRecord();
setRecordCount(this.recordCount - 1);
this.model.fireTableRowsDeleted(index, index);
}
});
}
protected void removeCachedRecords(final Iterable<? extends LayerRecord> records) {
for (final LayerRecord record : records) {
removeCachedRecord(record);
}
}
public void repaint() {
this.model.repaint();
}
protected void setRecordCount(final int recordCount) {
final int oldValue = getRecordCount();
this.recordCount = recordCount;
this.model.firePropertyChange("rowCount", oldValue, getRecordCount());
}
}