/* * Copyright (C) 2014 Alfons Wirtz * website www.freerouting.net * * This program 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. * * This program 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 at <http://www.gnu.org/licenses/> * for more details. * * UndoableObjects.java * * Created on 24. August 2003, 06:41 */ package datastructures; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ConcurrentMap; import java.util.Vector; /** * Database of objects, for which Undo and Redo operations are made possible. * The algorithm works only for objects containing no references. * * @author Alfons Wirtz */ public class UndoableObjects implements java.io.Serializable { /** Creates a new instance of UndoableObjectsList */ public UndoableObjects() { stack_level = 0; objects = new ConcurrentSkipListMap<Storable, UndoableObjectNode>(); deleted_objects_stack = new Vector<Collection<UndoableObjectNode>>(); } /** * Returns an iterator for sequential reading of the object list. * Use it together with this.read_object(). */ public Iterator<UndoableObjectNode> start_read_object() { Collection<UndoableObjectNode> object_list = objects.values(); return object_list.iterator(); } /** * Reads the next object in this list. * Returns null, if the list is exhausted. * p_it must be created by start_read_object. */ public UndoableObjects.Storable read_object(Iterator<UndoableObjectNode> p_it) { while (p_it.hasNext()) { UndoableObjectNode curr_node = p_it.next(); // skip objects getting alive only by redo if (curr_node != null && curr_node.level <= this.stack_level) { return (curr_node.object); } } return null; } /** * Adds p_object to the UndoableObjectsList. */ public void insert(UndoableObjects.Storable p_object) { disable_redo(); UndoableObjectNode curr_undoable_object = new UndoableObjectNode(p_object, stack_level); objects.put(p_object, curr_undoable_object); } /** * Removes p_object from the top level of the UndoableObjectsList. * Returns false, if p_object was not found in the list. */ public boolean delete(UndoableObjects.Storable p_object) { disable_redo(); Collection<UndoableObjectNode> curr_delete_list; if (deleted_objects_stack.isEmpty()) { // stack_level 0 curr_delete_list = null; } else { curr_delete_list = deleted_objects_stack.lastElement(); } // search p_object in the list UndoableObjectNode object_node = objects.get(p_object); if (object_node == null) { return false; } if (object_node.object != p_object) { System.out.println("UndoableObjectList.delete: Object inconsistent"); return false; } if (curr_delete_list != null) { if (object_node.level < this.stack_level) { // add curr_ob to the current delete list to make Undo possible. curr_delete_list.add(object_node); } else if (object_node.undo_object != null) { // add curr_ob.undo_object to the current delete list to make Undo possible. curr_delete_list.add(object_node.undo_object); } } objects.remove(p_object); return true; } /** * Makes the current state of the list restorable by Undo. */ public void generate_snapshot() { disable_redo(); Collection<UndoableObjectNode> curr_deleted_objects_list = new LinkedList<UndoableObjectNode>(); deleted_objects_stack.add(curr_deleted_objects_list); ++stack_level; } /** * Restores the situation before the last snapshot. * Outputs the cancelled and the restored objects (if != null) to enable * the calling function to take additional actions needed for these objects. * Returns false, if no more undo is possible */ public boolean undo(Collection<UndoableObjects.Storable> p_cancelled_objects, Collection<UndoableObjects.Storable> p_restored_objects) { if (stack_level == 0) { return false; // no more undo possible } Iterator<UndoableObjectNode> it = objects.values().iterator(); while (it.hasNext()) { UndoableObjectNode curr_node = it.next(); if (curr_node.level == stack_level) { if (curr_node.undo_object != null) { // replace the current object by its previous state. curr_node.undo_object.redo_object = curr_node; objects.put(curr_node.object, curr_node.undo_object); if (p_restored_objects != null) { p_restored_objects.add(curr_node.undo_object.object); } } if (p_cancelled_objects != null) { p_cancelled_objects.add(curr_node.object); } } } // restore the deleted objects Collection<UndoableObjectNode> curr_delete_list = deleted_objects_stack.elementAt(stack_level - 1); Iterator<UndoableObjectNode> it2 = curr_delete_list.iterator(); while (it2.hasNext()) { UndoableObjectNode curr_deleted_node = it2.next(); this.objects.put(curr_deleted_node.object, curr_deleted_node); if (p_restored_objects != null) { p_restored_objects.add(curr_deleted_node.object); } } --this.stack_level; redo_possible = true; return true; } /** * Restores the situation before the last undo. * Outputs the cancelled and the restored objects (if != null) to enable * the calling function to take additional actions needed for these objects. * Returns false, if no more redo is possible. */ public boolean redo(Collection<UndoableObjects.Storable> p_cancelled_objects, Collection<UndoableObjects.Storable> p_restored_objects) { if (this.stack_level >= deleted_objects_stack.size()) { return false; // alredy at the top level } ++this.stack_level; Iterator<UndoableObjectNode> it = objects.values().iterator(); while (it.hasNext()) { UndoableObjectNode curr_node = it.next(); if (curr_node.redo_object != null && curr_node.redo_object.level == this.stack_level) { // Object was created on a lower level and changed on the currenzt level, // replace the lower level object by the object on the current layer. objects.put(curr_node.object, curr_node.redo_object); if (p_cancelled_objects != null) { p_cancelled_objects.add(curr_node.object); } if (p_restored_objects != null) { p_restored_objects.add(curr_node.redo_object.object); // else the redo_object was deleted on the redo level } } else if (curr_node.level == this.stack_level) { // Object was created on the current level, allow it to be restored. p_restored_objects.add(curr_node.object); } } // Delete the objects, which were deleted on the current level, again. Collection<UndoableObjectNode> curr_delete_list = deleted_objects_stack.elementAt(stack_level - 1); Iterator<UndoableObjectNode> it2 = curr_delete_list.iterator(); while (it2.hasNext()) { UndoableObjectNode curr_deleted_node = it2.next(); while (curr_deleted_node.redo_object != null && curr_deleted_node.redo_object.level <= this.stack_level) { curr_deleted_node = curr_deleted_node.redo_object; } if (this.objects.remove(curr_deleted_node.object) == null) { System.out.println("previous deleted object not found"); } if (p_restored_objects == null || !p_restored_objects.remove(curr_deleted_node.object)) { // the object needs only be cancelled if it is already in the board if (p_cancelled_objects != null) { p_cancelled_objects.add(curr_deleted_node.object); } } } return true; } /** * Removes the top snapshot from the undo stack, so that its situation cannot be * restored any more. * Returns false, if no more snapshot could be popped. */ public boolean pop_snapshot() { disable_redo(); if (stack_level == 0) { return false; } Iterator<UndoableObjectNode> it = objects.values().iterator(); while (it.hasNext()) { UndoableObjectNode curr_node = it.next(); if (curr_node.level == stack_level - 1) { if (curr_node.redo_object != null && curr_node.redo_object.level == stack_level) { curr_node.redo_object.undo_object = curr_node.undo_object; if (curr_node.undo_object != null) { curr_node.undo_object.redo_object = curr_node.redo_object; } } } else if (curr_node.level >= stack_level) { --curr_node.level; } } int deleted_objects_stack_size = deleted_objects_stack.size(); if (deleted_objects_stack_size >= 2) { // join the top delete list with the delete list of the second top level Collection<UndoableObjectNode> from_delete_list = deleted_objects_stack.elementAt(deleted_objects_stack_size - 1); Collection<UndoableObjectNode> to_delete_list = deleted_objects_stack.elementAt(deleted_objects_stack_size - 2); for (UndoableObjectNode curr_deleted_node : from_delete_list) { if (curr_deleted_node.level < this.stack_level - 1) { to_delete_list.add(curr_deleted_node); } else if (curr_deleted_node.undo_object != null) { to_delete_list.add(curr_deleted_node.undo_object); } } } deleted_objects_stack.remove(deleted_objects_stack_size - 1); --stack_level; return true; } /** * Must be callel before p_object will be modified after a snapshot * for the first time, if it may have existed before that snapshot. */ public void save_for_undo(UndoableObjects.Storable p_object) { disable_redo(); // search p_object in the map UndoableObjectNode curr_node = objects.get(p_object); if (curr_node == null) { System.out.println("UndoableObjects.save_for_undo: object node not found"); return; } if (curr_node.level < this.stack_level) { UndoableObjectNode old_node = new UndoableObjectNode((UndoableObjects.Storable) p_object.clone(), curr_node.level); old_node.undo_object = curr_node.undo_object; old_node.redo_object = curr_node; curr_node.undo_object = old_node; curr_node.level = this.stack_level; return; } } /** * Must be called, if objects are changed for the first time after undo. */ private void disable_redo() { if (!redo_possible) { return; } redo_possible = false; // shorten the size of the deleted_objects_stack to this.stack_level for (int i = deleted_objects_stack.size() - 1; i >= this.stack_level; --i) { deleted_objects_stack.remove(i); } Iterator<UndoableObjectNode> it = objects.values().iterator(); while (it.hasNext()) { UndoableObjectNode curr_node = it.next(); if (curr_node.level > this.stack_level) { it.remove(); } else if (curr_node.level == this.stack_level) { curr_node.redo_object = null; } } } /** The entries of this map are of type UnduableObject, the keys of type UndoableObjects.Storable. */ private ConcurrentMap<Storable, UndoableObjectNode> objects; /** * the current undo level */ private int stack_level; /** * the lists of deleted objects on each undo level, which where already * existing before the previous snapshot. */ private Vector<Collection<UndoableObjectNode>> deleted_objects_stack; private boolean redo_possible = false; /** * Conditiom for an Object to be stored in an UndoableObjects database. * An object of class UndoableObjects.Storable must not contain any references. */ public interface Storable extends Comparable<Object> { /** * Creates an exact copy of this object * Public overwriting of the protected clone method in java.lang.Object, */ Object clone(); } /** * Stores informations for correct restoring or cancelling an object * in an undo or redo operation. * p_level is the level in the Undo stack, where this object was inserted. */ public static class UndoableObjectNode implements java.io.Serializable { /** Creates a new instance of UndoableObjectNode */ UndoableObjectNode(Storable p_object, int p_level) { object = p_object; level = p_level; undo_object = null; redo_object = null; } final Storable object; // the object in the node int level; // the level in the Undo stack, where this node was inserted UndoableObjectNode undo_object; // the object to restore in an undo or null. UndoableObjectNode redo_object; // the object to restore in a redo or null. } }