11package processing .app ;
22
3- import java .util .ArrayList ;
4- import java .util .List ;
3+ import java .util .LinkedList ;
4+ import java .util .ListIterator ;
55
66/**
77 * Keeps track of command history in console-like applications.
88 * @author P.J.S. Kools
99 */
1010public class CommandHistory {
1111
12- private List <String > commandHistory = new ArrayList <String >();
13- private int selectedCommandIndex = 0 ;
12+ private final LinkedList <String > commandHistory = new LinkedList <String >();
1413 private final int maxHistorySize ;
14+ private ListIterator <String > iterator = null ;
15+ private boolean iteratorAsc ;
1516
1617 /**
1718 * Create a new {@link CommandHistory}.
1819 * @param maxHistorySize - The max command history size.
1920 */
2021 public CommandHistory (int maxHistorySize ) {
2122 this .maxHistorySize = (maxHistorySize < 0 ? 0 : maxHistorySize );
22- this .commandHistory .add ("" ); // Current command placeholder.
23+ this .commandHistory .addLast ("" ); // Current command placeholder.
2324 }
2425
2526 /**
2627 * Adds the given command to the history and resets the history traversal
27- * position to the latest command. If the max history size is exceeded,
28- * the oldest command will be removed from the history.
28+ * position to the latest command. If the latest command in the history is
29+ * equal to the given command, it will not be added to the history.
30+ * If the max history size is exceeded, the oldest command will be removed
31+ * from the history.
2932 * @param command - The command to add.
3033 */
3134 public void addCommand (String command ) {
35+ if (this .maxHistorySize == 0 ) {
36+ return ;
37+ }
38+
39+ // Remove 'current' command.
40+ this .commandHistory .removeLast ();
41+
42+ // Add new command if it differs from the latest command.
43+ if (this .commandHistory .isEmpty ()
44+ || !this .commandHistory .getLast ().equals (command )) {
45+
46+ // Remove oldest command if max history size is exceeded.
47+ if (this .commandHistory .size () >= this .maxHistorySize ) {
48+ this .commandHistory .removeFirst ();
49+ }
3250
33- // Remove the oldest command if the max history size is exceeded.
34- if (this .commandHistory .size () >= this .maxHistorySize + 1 ) {
35- this .commandHistory .remove (0 );
51+ // Add new command and reset 'current' command.
52+ this .commandHistory .addLast (command );
3653 }
3754
38- // Add the new command, reset the 'current' command and reset the index.
39- this .commandHistory .set (this .commandHistory .size () - 1 , command );
40- this .commandHistory .add ("" ); // Current command placeholder.
41- this .selectedCommandIndex = this .commandHistory .size () - 1 ;
55+ // Re-add 'current' command and reset command iterator.
56+ this .commandHistory .addLast ("" ); // Current command placeholder.
57+ this .iterator = null ;
4258 }
4359
4460 /**
@@ -47,16 +63,35 @@ public void addCommand(String command) {
4763 * returns {@code false} otherwise.
4864 */
4965 public boolean hasNextCommand () {
50- return this .selectedCommandIndex + 1 < this .commandHistory .size ();
66+ if (this .iterator == null ) {
67+ return false ;
68+ }
69+ if (!this .iteratorAsc ) {
70+ this .iterator .next (); // Current command, ascending.
71+ this .iteratorAsc = true ;
72+ }
73+ return this .iterator .hasNext ();
5174 }
5275
5376 /**
5477 * Gets the next (more recent) command from the history.
5578 * @return The next command or {@code null} if no next command is available.
5679 */
5780 public String getNextCommand () {
58- return this .hasNextCommand ()
59- ? this .commandHistory .get (++this .selectedCommandIndex ) : null ;
81+
82+ // Return null if there is no next command available.
83+ if (!this .hasNextCommand ()) {
84+ return null ;
85+ }
86+
87+ // Get next command.
88+ String next = this .iterator .next ();
89+
90+ // Reset 'current' command when at the end of the list.
91+ if (this .iterator .nextIndex () == this .commandHistory .size ()) {
92+ this .iterator .set ("" ); // Reset 'current' command.
93+ }
94+ return next ;
6095 }
6196
6297 /**
@@ -65,15 +100,22 @@ public String getNextCommand() {
65100 * returns {@code false} otherwise.
66101 */
67102 public boolean hasPreviousCommand () {
68- return this .selectedCommandIndex > 0 ;
103+ if (this .iterator == null ) {
104+ return this .commandHistory .size () > 1 ;
105+ }
106+ if (this .iteratorAsc ) {
107+ this .iterator .previous (); // Current command, descending.
108+ this .iteratorAsc = false ;
109+ }
110+ return this .iterator .hasPrevious ();
69111 }
70112
71113 /**
72114 * Gets the previous (older) command from the history.
73115 * When this method is called while the most recent command in the history is
74116 * selected, this will store the current command as temporary latest command
75- * so that {@link #getNextCommand()} will return it. This temporary latest
76- * command gets reset when this case occurs again or when
117+ * so that {@link #getNextCommand()} will return it once . This temporary
118+ * latest command gets reset when this case occurs again or when
77119 * {@link #addCommand(String)} is invoked.
78120 * @param currentCommand - The current unexecuted command.
79121 * @return The previous command or {@code null} if no previous command is
@@ -86,14 +128,21 @@ public String getPreviousCommand(String currentCommand) {
86128 return null ;
87129 }
88130
89- // Store current unexecuted command if not traversing already.
90- if (this .selectedCommandIndex == this .commandHistory .size () - 1 ) {
91- this .commandHistory .set (this .commandHistory .size () - 1 ,
92- (currentCommand == null ? "" : currentCommand ));
131+ // Store current unexecuted command and create iterator if not traversing.
132+ if (this .iterator == null ) {
133+ this .iterator =
134+ this .commandHistory .listIterator (this .commandHistory .size ());
135+ this .iterator .previous (); // Last element, descending.
136+ this .iteratorAsc = false ;
137+ }
138+
139+ // Store current unexecuted command if on 'current' index.
140+ if (this .iterator .nextIndex () == this .commandHistory .size () - 1 ) {
141+ this .iterator .set (currentCommand == null ? "" : currentCommand );
93142 }
94143
95144 // Return the previous command.
96- return this .commandHistory . get (-- this . selectedCommandIndex );
145+ return this .iterator . previous ( );
97146 }
98147
99148 /**
@@ -103,16 +152,16 @@ public String getPreviousCommand(String currentCommand) {
103152 * was set.
104153 */
105154 public String resetHistoryLocation () {
106- this .selectedCommandIndex = this . commandHistory . size () - 1 ;
155+ this .iterator = null ;
107156 return this .commandHistory .set (this .commandHistory .size () - 1 , "" );
108157 }
109158
110159 /**
111160 * Clears the command history.
112161 */
113162 public void clear () {
163+ this .iterator = null ;
114164 this .commandHistory .clear ();
115- this .commandHistory .add ("" ); // Current command placeholder.
116- this .selectedCommandIndex = 0 ;
165+ this .commandHistory .addLast ("" ); // Current command placeholder.
117166 }
118167}
0 commit comments