001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.plugins.streetside.history;
003
004import java.util.ArrayList;
005import java.util.List;
006import java.util.Objects;
007
008import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;
009
010import org.openstreetmap.josm.plugins.streetside.history.commands.StreetsideCommand;
011import org.openstreetmap.josm.plugins.streetside.history.commands.StreetsideExecutableCommand;
012
013/**
014* History record system in order to let the user undo and redo commands.
015*
016* @author nokutu
017*
018*/
019public class StreetsideRecord {
020/** The unique instance of the class. */
021private static StreetsideRecord instance;
022
023private final List<StreetsideRecordListener> listeners = new ArrayList<>();
024
025/** The set of commands that have taken place or that have been undone. */
026public List<StreetsideCommand> commandList;
027/** Last written command. */
028public int position;
029
030/**
031* Main constructor.
032*/
033public StreetsideRecord() {
034 this.commandList = new ArrayList<>();
035 this.position = -1;
036}
037
038/**
039* Returns the unique instance of the class.
040*
041* @return The unique instance of the class.
042*/
043public static synchronized StreetsideRecord getInstance() {
044 if (StreetsideRecord.instance == null)
045   StreetsideRecord.instance = new StreetsideRecord();
046 return StreetsideRecord.instance;
047}
048
049/**
050* Adds a listener.
051*
052* @param lis
053*          The listener to be added.
054*/
055public void addListener(StreetsideRecordListener lis) {
056 this.listeners.add(lis);
057}
058
059/**
060* Removes the given listener.
061*
062* @param lis
063*          The listener to be removed.
064*/
065public void removeListener(StreetsideRecordListener lis) {
066 this.listeners.remove(lis);
067}
068
069/**
070* Adds a new command to the list.
071*
072* @param command
073*          The command to be added.
074*/
075public void addCommand(final StreetsideCommand command) {
076
077 if (command instanceof StreetsideExecutableCommand)
078   ((StreetsideExecutableCommand) command).execute();
079 // Checks if it is a continuation of last command
080 if (this.position != -1) {
081   boolean equalSets = true;
082   for (StreetsideAbstractImage img : this.commandList.get(this.position).images) {
083     equalSets = command.images.contains(img) && equalSets;
084   }
085   for (StreetsideAbstractImage img : command.images) {
086     equalSets = this.commandList.get(this.position).images.contains(img) && equalSets;
087   }
088   if (equalSets && this.commandList.get(this.position).getClass() == command.getClass()) {
089     this.commandList.get(this.position).sum(command);
090     fireRecordChanged();
091     return;
092   }
093 }
094 // Adds the command to the last position of the list.
095 this.commandList.add(this.position + 1, command);
096 this.position++;
097 while (this.commandList.size() > this.position + 1) {
098   this.commandList.remove(this.position + 1);
099 }
100 fireRecordChanged();
101}
102
103/**
104* Undo latest command.
105*/
106public void undo() {
107 if (this.position <= -1) {
108   return;
109 }
110 this.commandList.get(this.position).undo();
111 this.position--;
112 fireRecordChanged();
113}
114
115/**
116* Redoes latest undone action.
117*/
118public void redo() {
119 if (position + 1 >= commandList.size()) {
120   return;
121 }
122 this.position++;
123 this.commandList.get(this.position).redo();
124 fireRecordChanged();
125}
126
127private void fireRecordChanged() {
128 this.listeners.stream().filter(Objects::nonNull).forEach(StreetsideRecordListener::recordChanged);
129}
130
131/**
132* Resets the object to its start state.
133*/
134public void reset() {
135 this.commandList.clear();
136 this.position = -1;
137}
138}