View Javadoc

1   /*
2    * Copyright (c) 2002-2012, the original author or authors.
3    *
4    * This software is distributable under the BSD license. See the terms of the
5    * BSD license in the documentation provided with this software.
6    *
7    * http://www.opensource.org/licenses/bsd-license.php
8    */
9   package jline.console.history;
10  
11  import java.util.Iterator;
12  import java.util.LinkedList;
13  import java.util.ListIterator;
14  import java.util.NoSuchElementException;
15  
16  import static jline.internal.Preconditions.checkNotNull;
17  
18  /**
19   * Non-persistent {@link History}.
20   *
21   * @author <a href="mailto:mwp1@cornell.edu">Marc Prud'hommeaux</a>
22   * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
23   * @since 2.3
24   */
25  public class MemoryHistory
26      implements History
27  {
28      public static final int DEFAULT_MAX_SIZE = 500;
29  
30      private final LinkedList<CharSequence> items = new LinkedList<CharSequence>();
31  
32      private int maxSize = DEFAULT_MAX_SIZE;
33  
34      private boolean ignoreDuplicates = true;
35  
36      private boolean autoTrim = false;
37  
38      // NOTE: These are all ideas from looking at the Bash man page:
39  
40      // TODO: Add ignore space? (lines starting with a space are ignored)
41  
42      // TODO: Add ignore patterns?
43  
44      // TODO: Add history timestamp?
45  
46      // TODO: Add erase dups?
47  
48      private int offset = 0;
49  
50      private int index = 0;
51  
52      public void setMaxSize(final int maxSize) {
53          this.maxSize = maxSize;
54          maybeResize();
55      }
56  
57      public int getMaxSize() {
58          return maxSize;
59      }
60  
61      public boolean isIgnoreDuplicates() {
62          return ignoreDuplicates;
63      }
64  
65      public void setIgnoreDuplicates(final boolean flag) {
66          this.ignoreDuplicates = flag;
67      }
68  
69      public boolean isAutoTrim() {
70          return autoTrim;
71      }
72  
73      public void setAutoTrim(final boolean flag) {
74          this.autoTrim = flag;
75      }
76  
77      public int size() {
78          return items.size();
79      }
80  
81      public boolean isEmpty() {
82          return items.isEmpty();
83      }
84  
85      public int index() {
86          return offset + index;
87      }
88  
89      public void clear() {
90          items.clear();
91          offset = 0;
92          index = 0;
93      }
94  
95      public CharSequence get(final int index) {
96          return items.get(index - offset);
97      }
98  
99      public void set(int index, CharSequence item) {
100         items.set(index - offset, item);
101     }
102 
103     public void add(CharSequence item) {
104         checkNotNull(item);
105 
106         if (isAutoTrim()) {
107             item = String.valueOf(item).trim();
108         }
109 
110         if (isIgnoreDuplicates()) {
111             if (!items.isEmpty() && item.equals(items.getLast())) {
112                 return;
113             }
114         }
115 
116         internalAdd(item);
117     }
118 
119     public CharSequence remove(int i) {
120         return items.remove(i);
121     }
122 
123     public CharSequence removeFirst() {
124         return items.removeFirst();
125     }
126 
127     public CharSequence removeLast() {
128         return items.removeLast();
129     }
130 
131     protected void internalAdd(CharSequence item) {
132         items.add(item);
133 
134         maybeResize();
135     }
136 
137     public void replace(final CharSequence item) {
138         items.removeLast();
139         add(item);
140     }
141 
142     private void maybeResize() {
143         while (size() > getMaxSize()) {
144             items.removeFirst();
145             offset++;
146         }
147 
148         index = size();
149     }
150 
151     public ListIterator<Entry> entries(final int index) {
152         return new EntriesIterator(index - offset);
153     }
154 
155     public ListIterator<Entry> entries() {
156         return entries(offset);
157     }
158 
159     public Iterator<Entry> iterator() {
160         return entries();
161     }
162 
163     private static class EntryImpl
164         implements Entry
165     {
166         private final int index;
167 
168         private final CharSequence value;
169 
170         public EntryImpl(int index, CharSequence value) {
171             this.index = index;
172             this.value = value;
173         }
174 
175         public int index() {
176             return index;
177         }
178 
179         public CharSequence value() {
180             return value;
181         }
182 
183         @Override
184         public String toString() {
185             return String.format("%d: %s", index, value);
186         }
187     }
188 
189     private class EntriesIterator
190         implements ListIterator<Entry>
191     {
192         private final ListIterator<CharSequence> source;
193 
194         private EntriesIterator(final int index) {
195             source = items.listIterator(index);
196         }
197 
198         public Entry next() {
199             if (!source.hasNext()) {
200                 throw new NoSuchElementException();
201             }
202             return new EntryImpl(offset + source.nextIndex(), source.next());
203         }
204 
205         public Entry previous() {
206             if (!source.hasPrevious()) {
207                 throw new NoSuchElementException();
208             }
209             return new EntryImpl(offset + source.previousIndex(), source.previous());
210         }
211 
212         public int nextIndex() {
213             return offset + source.nextIndex();
214         }
215 
216         public int previousIndex() {
217             return offset + source.previousIndex();
218         }
219 
220         public boolean hasNext() {
221             return source.hasNext();
222         }
223 
224         public boolean hasPrevious() {
225             return source.hasPrevious();
226         }
227 
228         public void remove() {
229             throw new UnsupportedOperationException();
230         }
231 
232         public void set(final Entry entry) {
233             throw new UnsupportedOperationException();
234         }
235 
236         public void add(final Entry entry) {
237             throw new UnsupportedOperationException();
238         }
239     }
240 
241     //
242     // Navigation
243     //
244 
245     /**
246      * This moves the history to the last entry. This entry is one position
247      * before the moveToEnd() position.
248      *
249      * @return Returns false if there were no history entries or the history
250      *         index was already at the last entry.
251      */
252     public boolean moveToLast() {
253         int lastEntry = size() - 1;
254         if (lastEntry >= 0 && lastEntry != index) {
255             index = size() - 1;
256             return true;
257         }
258 
259         return false;
260     }
261 
262     /**
263      * Move to the specified index in the history
264      * @param index
265      * @return
266      */
267     public boolean moveTo(int index) {
268         index -= offset;
269         if (index >= 0 && index < size() ) {
270             this.index = index;
271             return true;
272         }
273         return false;
274     }
275 
276     /**
277      * Moves the history index to the first entry.
278      *
279      * @return Return false if there are no entries in the history or if the
280      *         history is already at the beginning.
281      */
282     public boolean moveToFirst() {
283         if (size() > 0 && index != 0) {
284             index = 0;
285             return true;
286         }
287 
288         return false;
289     }
290 
291     /**
292      * Move to the end of the history buffer. This will be a blank entry, after
293      * all of the other entries.
294      */
295     public void moveToEnd() {
296         index = size();
297     }
298 
299     /**
300      * Return the content of the current buffer.
301      */
302     public CharSequence current() {
303         if (index >= size()) {
304             return "";
305         }
306 
307         return items.get(index);
308     }
309 
310     /**
311      * Move the pointer to the previous element in the buffer.
312      *
313      * @return true if we successfully went to the previous element
314      */
315     public boolean previous() {
316         if (index <= 0) {
317             return false;
318         }
319 
320         index--;
321 
322         return true;
323     }
324 
325     /**
326      * Move the pointer to the next element in the buffer.
327      *
328      * @return true if we successfully went to the next element
329      */
330     public boolean next() {
331         if (index >= size()) {
332             return false;
333         }
334 
335         index++;
336 
337         return true;
338     }
339 
340     @Override
341     public String toString() {
342         StringBuilder sb = new StringBuilder();
343         for (Entry e : this) {
344             sb.append(e.toString() + "\n");
345         }
346         return sb.toString();
347     }
348 }