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 jdk.internal.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 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  */
  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 }