Skip to content

Commit

Permalink
CommandHistory optimization
Browse files Browse the repository at this point in the history
- Use LinkedList with ListIterator to make all methods except for `clear()` run in `O(1)` (constant runtime) instead of `O(n)` (linear runtime).
- No longer store executed commands that are executed multiple times (executing {1, 1, 1, 1, 2} now only adds {1, 2} to the history).
  • Loading branch information
Pieter12345 authored and facchinm committed Jul 18, 2019
1 parent f5b3831 commit 1d21f0c
Showing 1 changed file with 77 additions and 28 deletions.
105 changes: 77 additions & 28 deletions app/src/processing/app/CommandHistory.java
Original file line number Diff line number Diff line change
@@ -1,44 +1,60 @@
package processing.app;

import java.util.ArrayList;
import java.util.List;
import java.util.LinkedList;
import java.util.ListIterator;

/**
* Keeps track of command history in console-like applications.
* @author P.J.S. Kools
*/
public class CommandHistory {

private List<String> commandHistory = new ArrayList<String>();
private int selectedCommandIndex = 0;
private final LinkedList<String> commandHistory = new LinkedList<String>();
private final int maxHistorySize;
private ListIterator<String> iterator = null;
private boolean iteratorAsc;

/**
* Create a new {@link CommandHistory}.
* @param maxHistorySize - The max command history size.
*/
public CommandHistory(int maxHistorySize) {
this.maxHistorySize = (maxHistorySize < 0 ? 0 : maxHistorySize);
this.commandHistory.add(""); // Current command placeholder.
this.commandHistory.addLast(""); // Current command placeholder.
}

/**
* Adds the given command to the history and resets the history traversal
* position to the latest command. If the max history size is exceeded,
* the oldest command will be removed from the history.
* position to the latest command. If the latest command in the history is
* equal to the given command, it will not be added to the history.
* If the max history size is exceeded, the oldest command will be removed
* from the history.
* @param command - The command to add.
*/
public void addCommand(String command) {
if (this.maxHistorySize == 0) {
return;
}

// Remove 'current' command.
this.commandHistory.removeLast();

// Add new command if it differs from the latest command.
if (this.commandHistory.isEmpty()
|| !this.commandHistory.getLast().equals(command)) {

// Remove oldest command if max history size is exceeded.
if (this.commandHistory.size() >= this.maxHistorySize) {
this.commandHistory.removeFirst();
}

// Remove the oldest command if the max history size is exceeded.
if(this.commandHistory.size() >= this.maxHistorySize + 1) {
this.commandHistory.remove(0);
// Add new command and reset 'current' command.
this.commandHistory.addLast(command);
}

// Add the new command, reset the 'current' command and reset the index.
this.commandHistory.set(this.commandHistory.size() - 1, command);
this.commandHistory.add(""); // Current command placeholder.
this.selectedCommandIndex = this.commandHistory.size() - 1;
// Re-add 'current' command and reset command iterator.
this.commandHistory.addLast(""); // Current command placeholder.
this.iterator = null;
}

/**
Expand All @@ -47,16 +63,35 @@ public void addCommand(String command) {
* returns {@code false} otherwise.
*/
public boolean hasNextCommand() {
return this.selectedCommandIndex + 1 < this.commandHistory.size();
if (this.iterator == null) {
return false;
}
if (!this.iteratorAsc) {
this.iterator.next(); // Current command, ascending.
this.iteratorAsc = true;
}
return this.iterator.hasNext();
}

/**
* Gets the next (more recent) command from the history.
* @return The next command or {@code null} if no next command is available.
*/
public String getNextCommand() {
return this.hasNextCommand()
? this.commandHistory.get(++this.selectedCommandIndex) : null;

// Return null if there is no next command available.
if (!this.hasNextCommand()) {
return null;
}

// Get next command.
String next = this.iterator.next();

// Reset 'current' command when at the end of the list.
if (this.iterator.nextIndex() == this.commandHistory.size()) {
this.iterator.set(""); // Reset 'current' command.
}
return next;
}

/**
Expand All @@ -65,15 +100,22 @@ public String getNextCommand() {
* returns {@code false} otherwise.
*/
public boolean hasPreviousCommand() {
return this.selectedCommandIndex > 0;
if (this.iterator == null) {
return this.commandHistory.size() > 1;
}
if (this.iteratorAsc) {
this.iterator.previous(); // Current command, descending.
this.iteratorAsc = false;
}
return this.iterator.hasPrevious();
}

/**
* Gets the previous (older) command from the history.
* When this method is called while the most recent command in the history is
* selected, this will store the current command as temporary latest command
* so that {@link #getNextCommand()} will return it. This temporary latest
* command gets reset when this case occurs again or when
* so that {@link #getNextCommand()} will return it once. This temporary
* latest command gets reset when this case occurs again or when
* {@link #addCommand(String)} is invoked.
* @param currentCommand - The current unexecuted command.
* @return The previous command or {@code null} if no previous command is
Expand All @@ -86,14 +128,21 @@ public String getPreviousCommand(String currentCommand) {
return null;
}

// Store current unexecuted command if not traversing already.
if (this.selectedCommandIndex == this.commandHistory.size() - 1) {
this.commandHistory.set(this.commandHistory.size() - 1,
(currentCommand == null ? "" : currentCommand));
// Store current unexecuted command and create iterator if not traversing.
if (this.iterator == null) {
this.iterator =
this.commandHistory.listIterator(this.commandHistory.size());
this.iterator.previous(); // Last element, descending.
this.iteratorAsc = false;
}

// Store current unexecuted command if on 'current' index.
if (this.iterator.nextIndex() == this.commandHistory.size() - 1) {
this.iterator.set(currentCommand == null ? "" : currentCommand);
}

// Return the previous command.
return this.commandHistory.get(--this.selectedCommandIndex);
return this.iterator.previous();
}

/**
Expand All @@ -103,16 +152,16 @@ public String getPreviousCommand(String currentCommand) {
* was set.
*/
public String resetHistoryLocation() {
this.selectedCommandIndex = this.commandHistory.size() - 1;
this.iterator = null;
return this.commandHistory.set(this.commandHistory.size() - 1, "");
}

/**
* Clears the command history.
*/
public void clear() {
this.iterator = null;
this.commandHistory.clear();
this.commandHistory.add(""); // Current command placeholder.
this.selectedCommandIndex = 0;
this.commandHistory.addLast(""); // Current command placeholder.
}
}

0 comments on commit 1d21f0c

Please sign in to comment.