    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}