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 )) {
3245
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 );
46+ // Remove oldest command if max history size is exceeded.
47+ if (this .commandHistory .size () >= this .maxHistorySize ) {
48+ this .commandHistory .removeFirst ();
49+ }
50+
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,22 @@ 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+ return this .hasNextCommand () ? this .iterator .next () : null ;
6082 }
6183
6284 /**
@@ -65,7 +87,14 @@ public String getNextCommand() {
6587 * returns {@code false} otherwise.
6688 */
6789 public boolean hasPreviousCommand () {
68- return this .selectedCommandIndex > 0 ;
90+ if (this .iterator == null ) {
91+ return this .commandHistory .size () > 1 ;
92+ }
93+ if (this .iteratorAsc ) {
94+ this .iterator .previous (); // Current command, descending.
95+ this .iteratorAsc = false ;
96+ }
97+ return this .iterator .hasPrevious ();
6998 }
7099
71100 /**
@@ -86,14 +115,21 @@ public String getPreviousCommand(String currentCommand) {
86115 return null ;
87116 }
88117
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 ));
118+ // Store current unexecuted command and create iterator if not traversing.
119+ if (this .iterator == null ) {
120+ this .iterator =
121+ this .commandHistory .listIterator (this .commandHistory .size ());
122+ this .iterator .previous (); // Last element, descending.
123+ this .iteratorAsc = false ;
124+ }
125+
126+ // Store current unexecuted command if on 'current' index.
127+ if (this .iterator .nextIndex () == this .commandHistory .size () - 1 ) {
128+ this .iterator .set (currentCommand == null ? "" : currentCommand );
93129 }
94130
95131 // Return the previous command.
96- return this .commandHistory . get (-- this . selectedCommandIndex );
132+ return this .iterator . previous ( );
97133 }
98134
99135 /**
@@ -103,16 +139,16 @@ public String getPreviousCommand(String currentCommand) {
103139 * was set.
104140 */
105141 public String resetHistoryLocation () {
106- this .selectedCommandIndex = this . commandHistory . size () - 1 ;
142+ this .iterator = null ;
107143 return this .commandHistory .set (this .commandHistory .size () - 1 , "" );
108144 }
109145
110146 /**
111147 * Clears the command history.
112148 */
113149 public void clear () {
150+ this .iterator = null ;
114151 this .commandHistory .clear ();
115- this .commandHistory .add ("" ); // Current command placeholder.
116- this .selectedCommandIndex = 0 ;
152+ this .commandHistory .addLast ("" ); // Current command placeholder.
117153 }
118154}
0 commit comments