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