All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.jonestimd.swing.table.model.ChangeTracker Maven / Gradle / Ivy

There is a newer version: 1.4.5
Show newest version
// Copyright (c) 2016 Timothy D. Jones
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package io.github.jonestimd.swing.table.model;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Stream;

import io.github.jonestimd.collection.IdentityArrayList;

import static java.util.stream.Stream.*;

/**
 * Base class for tracking unsaved changes in a table.  Unsaved changes include
 * 
    *
  • unsaved new rows
  • *
  • unsaved deletes
  • *
  • unsaved modifications to rows
  • *
* Unsaved modifications to rows are keyed by the row bean and a property index (typically the index of the table column * displaying the property). * @param the class representing a row in the table */ public abstract class ChangeTracker { private final List pendingAdds; private final Map> originalValues; private final List pendingDeletes; /** * @param useEquals if true then use {@link Object#equals(Object)} to match rows, otherwise use object identity */ protected ChangeTracker(boolean useEquals) { if (useEquals) { pendingAdds = new ArrayList<>(); originalValues = new HashMap<>(); pendingDeletes = new ArrayList<>(); } else { pendingAdds = new IdentityArrayList<>(); originalValues = new IdentityHashMap<>(); pendingDeletes = new IdentityArrayList<>(); } } /** * Mark a row as a pending add. */ public void pendingAdd(T item) { pendingAdds.add(item); } /** * Mark a row as a pending delete. */ public void pendingDelete(T item) { if (!pendingAdds.remove(item)) { pendingDeletes.add(item); } } /** * Cancel pending deletes matching a predicate. */ public void cancelDeletes(Predicate predicate) { pendingDeletes.removeIf(predicate); } /** * Reset pending changes for a row. */ public void resetItem(T item) { pendingAdds.remove(item); originalValues.remove(item); pendingDeletes.remove(item); } /** * Reset pending changes for rows matching a predicate. */ public void resetItems(Predicate predicate) { pendingAdds.removeIf(predicate); originalValues.keySet().removeIf(predicate); pendingDeletes.removeIf(predicate); } /** * @return true if the row is a pending add. */ public boolean isPendingAdd(T item) { return pendingAdds.contains(item); } /** * @return true if the row is a pending delete. */ public boolean isPendingDelete(T item) { return pendingDeletes.contains(item); } /** * Add or remove a pending change for a row property. * @param item the row bean * @param index the property index * @param oldValue the previous value of the property * @param newValue the replacement value for the property */ public void setValue(T item, int index, Object oldValue, Object newValue) { if (!pendingAdds.contains(item)) { Map changes = originalValues.get(item); if (changes == null) { changes = new HashMap<>(); changes.put(index, oldValue); originalValues.put(item, changes); } else if (changes.containsKey(index)) { if (Objects.equals(newValue, changes.get(index))) { changes.remove(index); if (changes.isEmpty()) { originalValues.remove(item); } } } else { changes.put(index, oldValue); } } } /** * @return true if there are no pending changes for any rows. */ public boolean isEmpty() { return pendingAdds.isEmpty() && originalValues.isEmpty() && pendingDeletes.isEmpty(); } /** * @param item a row bean * @param index a property index * @return true if the row property has been modified. */ public boolean isChanged(T item, int index) { return pendingAdds.contains(item) || pendingDeletes.contains(item) || originalValues.containsKey(item) && originalValues.get(item).containsKey(index); } public Set getChangeIndexes(T item) { return originalValues.getOrDefault(item, Collections.emptyMap()).keySet(); } /** * @return all of the rows with pending changes. */ public Stream getChanges() { return concat(pendingAdds.stream(), concat(originalValues.keySet().stream(), pendingDeletes.stream())); } /** * @return unsaved new rows. */ public List getAdds() { return Collections.unmodifiableList(pendingAdds); } /** * @return rows pending deletion. */ public List getDeletes() { return Collections.unmodifiableList(pendingDeletes); } /** * @return unsaved new and modified rows. */ public Stream getUpdates() { return Stream.concat(pendingAdds.stream(), originalValues.keySet().stream()); } /** * Restore the original value of a modified property of a row. If the specified proeprty has been modified * then {@link #revertItemChange(Object, Object, int)} is called with the original value. * @param item the row to update * @param index the property index */ public void undoChange(T item, int index) { Map changes = originalValues.get(item); if (changes != null && changes.containsKey(index)) { Object originalValue = changes.remove(index); revertItemChange(originalValue, item, index); if (changes.isEmpty()) { originalValues.remove(item); } } } public void undoDelete(T item) { if (pendingDeletes.remove(item)) { itemUpdated(item); } } /** * Revert all pending changes. */ public void revert() { while (!pendingAdds.isEmpty()) { itemDeleted(pendingAdds.remove(0)); } while (!pendingDeletes.isEmpty()) { itemUpdated(pendingDeletes.remove(0)); } for (Iterator>> iter = originalValues.entrySet().iterator(); iter.hasNext(); ) { Map.Entry> entry = iter.next(); T item = entry.getKey(); for (Map.Entry changeEntry : entry.getValue().entrySet()) { revertItemChange(changeEntry.getValue(), item, changeEntry.getKey()); } iter.remove(); } } /** * Implementation should update the table model with the original value without notifying this change tracker. */ protected abstract void revertItemChange(Object originalValue, T item, int index); /** * Called when the modification status of a row changes. Implementation should fire table model change events. */ protected abstract void itemUpdated(T item); /** * Implementation should remove the row from the table without notifying this change tracker. */ protected abstract void itemDeleted(T item); /** * Commit all pending changes. */ public void commit() { while (!pendingAdds.isEmpty()) { itemUpdated(pendingAdds.remove(0)); } while (!pendingDeletes.isEmpty()) { itemDeleted(pendingDeletes.remove(0)); } for (Iterator iter = originalValues.keySet().iterator(); iter.hasNext(); ) { T item = iter.next(); iter.remove(); itemUpdated(item); } } /** * Clear all pending changes. */ public void reset() { pendingAdds.clear(); originalValues.clear(); pendingDeletes.clear(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy