1
0
mirror of https://github.com/arduino/Arduino.git synced 2024-11-29 10:24:12 +01:00

CommandHistory optimization

- 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).
This commit is contained in:
Pieter12345 2019-03-30 23:16:19 +01:00 committed by Martino Facchin
parent f5b383113f
commit 1d21f0cca3

View File

@ -1,7 +1,7 @@
package processing.app; package processing.app;
import java.util.ArrayList; import java.util.LinkedList;
import java.util.List; import java.util.ListIterator;
/** /**
* Keeps track of command history in console-like applications. * Keeps track of command history in console-like applications.
@ -9,9 +9,10 @@ import java.util.List;
*/ */
public class CommandHistory { public class CommandHistory {
private List<String> commandHistory = new ArrayList<String>(); private final LinkedList<String> commandHistory = new LinkedList<String>();
private int selectedCommandIndex = 0;
private final int maxHistorySize; private final int maxHistorySize;
private ListIterator<String> iterator = null;
private boolean iteratorAsc;
/** /**
* Create a new {@link CommandHistory}. * Create a new {@link CommandHistory}.
@ -19,26 +20,41 @@ public class CommandHistory {
*/ */
public CommandHistory(int maxHistorySize) { public CommandHistory(int maxHistorySize) {
this.maxHistorySize = (maxHistorySize < 0 ? 0 : 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 * Adds the given command to the history and resets the history traversal
* position to the latest command. If the max history size is exceeded, * position to the latest command. If the latest command in the history is
* the oldest command will be removed from the history. * 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. * @param command - The command to add.
*/ */
public void addCommand(String command) { public void addCommand(String command) {
if (this.maxHistorySize == 0) {
// Remove the oldest command if the max history size is exceeded. return;
if(this.commandHistory.size() >= this.maxHistorySize + 1) {
this.commandHistory.remove(0);
} }
// Add the new command, reset the 'current' command and reset the index. // Remove 'current' command.
this.commandHistory.set(this.commandHistory.size() - 1, command); this.commandHistory.removeLast();
this.commandHistory.add(""); // Current command placeholder.
this.selectedCommandIndex = this.commandHistory.size() - 1; // 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();
}
// Add new command and reset 'current' command.
this.commandHistory.addLast(command);
}
// Re-add 'current' command and reset command iterator.
this.commandHistory.addLast(""); // Current command placeholder.
this.iterator = null;
} }
/** /**
@ -47,7 +63,14 @@ public class CommandHistory {
* returns {@code false} otherwise. * returns {@code false} otherwise.
*/ */
public boolean hasNextCommand() { 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();
} }
/** /**
@ -55,8 +78,20 @@ public class CommandHistory {
* @return The next command or {@code null} if no next command is available. * @return The next command or {@code null} if no next command is available.
*/ */
public String getNextCommand() { 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;
} }
/** /**
@ -65,15 +100,22 @@ public class CommandHistory {
* returns {@code false} otherwise. * returns {@code false} otherwise.
*/ */
public boolean hasPreviousCommand() { 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. * Gets the previous (older) command from the history.
* When this method is called while the most recent command in the history is * 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 * selected, this will store the current command as temporary latest command
* so that {@link #getNextCommand()} will return it. This temporary latest * so that {@link #getNextCommand()} will return it once. This temporary
* command gets reset when this case occurs again or when * latest command gets reset when this case occurs again or when
* {@link #addCommand(String)} is invoked. * {@link #addCommand(String)} is invoked.
* @param currentCommand - The current unexecuted command. * @param currentCommand - The current unexecuted command.
* @return The previous command or {@code null} if no previous command is * @return The previous command or {@code null} if no previous command is
@ -86,14 +128,21 @@ public class CommandHistory {
return null; return null;
} }
// Store current unexecuted command if not traversing already. // Store current unexecuted command and create iterator if not traversing.
if (this.selectedCommandIndex == this.commandHistory.size() - 1) { if (this.iterator == null) {
this.commandHistory.set(this.commandHistory.size() - 1, this.iterator =
(currentCommand == null ? "" : currentCommand)); 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 the previous command.
return this.commandHistory.get(--this.selectedCommandIndex); return this.iterator.previous();
} }
/** /**
@ -103,7 +152,7 @@ public class CommandHistory {
* was set. * was set.
*/ */
public String resetHistoryLocation() { public String resetHistoryLocation() {
this.selectedCommandIndex = this.commandHistory.size() - 1; this.iterator = null;
return this.commandHistory.set(this.commandHistory.size() - 1, ""); return this.commandHistory.set(this.commandHistory.size() - 1, "");
} }
@ -111,8 +160,8 @@ public class CommandHistory {
* Clears the command history. * Clears the command history.
*/ */
public void clear() { public void clear() {
this.iterator = null;
this.commandHistory.clear(); this.commandHistory.clear();
this.commandHistory.add(""); // Current command placeholder. this.commandHistory.addLast(""); // Current command placeholder.
this.selectedCommandIndex = 0;
} }
} }