    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}