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 */
9package jdk.internal.jline.console.history;
10
11import java.util.Iterator;
12import java.util.LinkedList;
13import java.util.ListIterator;
14import java.util.NoSuchElementException;
15
16import static jdk.internal.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 */
25public 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}
349