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 }