Coverage Report - org.crosswire.common.history.History
 
Classes in this File Line Coverage Branch Coverage Complexity
History
0%
0/54
0%
0/34
2.833
 
 1  
 /**
 2  
  * Distribution License:
 3  
  * JSword is free software; you can redistribute it and/or modify it under
 4  
  * the terms of the GNU Lesser General Public License, version 2.1 or later
 5  
  * as published by the Free Software Foundation. This program is distributed
 6  
  * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 7  
  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 8  
  * See the GNU Lesser General Public License for more details.
 9  
  *
 10  
  * The License is available on the internet at:
 11  
  *      http://www.gnu.org/copyleft/lgpl.html
 12  
  * or by writing to:
 13  
  *      Free Software Foundation, Inc.
 14  
  *      59 Temple Place - Suite 330
 15  
  *      Boston, MA 02111-1307, USA
 16  
  *
 17  
  * © CrossWire Bible Society, 2005 - 2016
 18  
  *
 19  
  */
 20  
 package org.crosswire.common.history;
 21  
 
 22  
 import java.util.ArrayList;
 23  
 import java.util.Collections;
 24  
 import java.util.HashMap;
 25  
 import java.util.List;
 26  
 import java.util.Map;
 27  
 import java.util.concurrent.CopyOnWriteArrayList;
 28  
 
 29  
 /**
 30  
  * Maintains a navigable history of objects. This maintains a dated list of
 31  
  * objects and a current navigation list.
 32  
  * 
 33  
  * @see gnu.lgpl.License The GNU Lesser General Public License for details.
 34  
  * @author DM Smith
 35  
  */
 36  
 public class History {
 37  
     /**
 38  
      * Create an empty navigation and history list.
 39  
      * 
 40  
      */
 41  0
     public History() {
 42  0
         nav = new ArrayList<Object>();
 43  0
         history = new HashMap<Object, Long>();
 44  0
         listeners = new CopyOnWriteArrayList<HistoryListener>();
 45  0
     }
 46  
 
 47  
     /**
 48  
      * Make a particular element in the navigation list the current item in
 49  
      * history.
 50  
      * 
 51  
      * @param index
 52  
      *            the index of item to make the last one in the back list, -1
 53  
      *            (or lower) will put everything in the forward list. Indexes
 54  
      *            beyond the end of the list will put everything in the back
 55  
      *            list.
 56  
      * @return the specified item in history
 57  
      */
 58  
     public Object select(int index) {
 59  0
         int i = index;
 60  
 
 61  
         // Adjust to be 1 based
 62  0
         int size = nav.size();
 63  
 
 64  0
         if (i > size) {
 65  0
             i = size;
 66  0
         } else if (i < 1) {
 67  0
             i = 1;
 68  
         }
 69  
 
 70  
         // Only fire history changes when there is a change.
 71  0
         if (i != backCount) {
 72  0
             backCount = i;
 73  0
             fireHistoryChanged();
 74  
         }
 75  
 
 76  0
         return getCurrent();
 77  
     }
 78  
 
 79  
     /**
 80  
      * Add an element to history. If the element is in the forward list, then it
 81  
      * replaces everything in the forward list upto it. Otherwise, it replaces
 82  
      * the forward list.
 83  
      * 
 84  
      * @param obj
 85  
      *            the object to add
 86  
      */
 87  
     public void add(Object obj) {
 88  0
         Object current = getCurrent();
 89  
 
 90  
         // Don't add null objects or the same object.
 91  0
         if (obj == null || obj.equals(current)) {
 92  0
             return;
 93  
         }
 94  
 
 95  
         // If we are adding the next element, then just advance
 96  
         // otherwise ...
 97  
         // Object next = peek(1);
 98  
         // if (!obj.equals(next))
 99  
         // {
 100  0
         int size = nav.size();
 101  0
         if (size > backCount) {
 102  0
             int pos = backCount;
 103  0
             while (pos < size && !obj.equals(nav.get(pos))) {
 104  0
                 pos++;
 105  
             }
 106  
             // At this point pos either == size or the element at pos matches
 107  
             // what we are navigating to.
 108  0
             nav.subList(backCount, Math.min(pos, size)).clear();
 109  
         }
 110  
 
 111  
         // If it matches, then we don't have to do anything more
 112  0
         if (!obj.equals(peek(1))) {
 113  
             // then we add it
 114  0
             nav.add(backCount, obj);
 115  
         }
 116  
         // }
 117  
 
 118  0
         backCount++;
 119  
 
 120  
         // and remember when we saw it
 121  0
         visit(obj);
 122  
 
 123  0
         fireHistoryChanged();
 124  0
     }
 125  
 
 126  
     /**
 127  
      * Get all the elements in "back" list.
 128  
      * 
 129  
      * @return the elements in the back list.
 130  
      */
 131  
     public List<Object> getPreviousList() {
 132  0
         if (backCount > 0) {
 133  0
             return Collections.unmodifiableList(nav.subList(0, backCount));
 134  
         }
 135  0
         return Collections.emptyList();
 136  
     }
 137  
 
 138  
     /**
 139  
      * Get all the elements in the "forward" list.
 140  
      * 
 141  
      * @return the elements in the forward list.
 142  
      */
 143  
     public List<Object> getNextList() {
 144  0
         if (backCount < nav.size()) {
 145  0
             return Collections.unmodifiableList(nav.subList(backCount, nav.size()));
 146  
         }
 147  0
         return Collections.emptyList();
 148  
     }
 149  
 
 150  
     /**
 151  
      * Increments the current history item by the given amount. Positive numbers
 152  
      * are forward. Negative numbers are back.
 153  
      * 
 154  
      * @param i
 155  
      *            the distance to travel
 156  
      * @return the item at the requested location, or at the end of the list if
 157  
      *         i is too big, or at the beginning of the list if i is too small,
 158  
      *         otherwise null.
 159  
      */
 160  
     public Object go(int i) {
 161  0
         return select(backCount + i);
 162  
     }
 163  
 
 164  
     /**
 165  
      * Get the current item in the "back" list
 166  
      * 
 167  
      * @return the current item in the back list.
 168  
      */
 169  
     public Object getCurrent() {
 170  0
         if (!nav.isEmpty() && backCount > 0) {
 171  0
             return nav.get(backCount - 1);
 172  
         }
 173  0
         return null;
 174  
     }
 175  
 
 176  
     /**
 177  
      * Get the current item in the "back" list
 178  
      * 
 179  
      * @param i
 180  
      *            the distance to travel
 181  
      * @return the requested item in the navigation list.
 182  
      */
 183  
     private Object peek(int i) {
 184  0
         int size = nav.size();
 185  0
         if (size > 0 && backCount > 0 && backCount + i <= size) {
 186  0
             return nav.get(backCount + i - 1);
 187  
         }
 188  0
         return null;
 189  
     }
 190  
 
 191  
     /**
 192  
      * Add a listener for history events.
 193  
      * 
 194  
      * @param li
 195  
      *            the interested listener
 196  
      */
 197  
     public void addHistoryListener(HistoryListener li) {
 198  0
         listeners.add(li);
 199  0
     }
 200  
 
 201  
     /**
 202  
      * Remove a listener of history events.
 203  
      * 
 204  
      * @param li
 205  
      *            the disinterested listener
 206  
      */
 207  
     public void removeHistoryListener(HistoryListener li) {
 208  0
         listeners.remove(li);
 209  0
     }
 210  
 
 211  
     /**
 212  
      * Note that this object has been seen at this time.
 213  
      * 
 214  
      * @param obj the object that has been seen
 215  
      */
 216  
     private void visit(Object obj) {
 217  0
         history.put(obj, Long.valueOf(System.currentTimeMillis()));
 218  0
     }
 219  
 
 220  
     /**
 221  
      * Kick of an event sequence
 222  
      */
 223  
     private void fireHistoryChanged() {
 224  0
         HistoryEvent ev = new HistoryEvent(this);
 225  0
         for (HistoryListener listener: listeners) {
 226  0
             listener.historyChanged(ev);
 227  
         }
 228  0
     }
 229  
 
 230  
     /**
 231  
      * The elements that can be navigated.
 232  
      */
 233  
     private List<Object> nav;
 234  
 
 235  
     /**
 236  
      * A map of elements that have been seen so far to when they have been seen.
 237  
      */
 238  
     private Map<Object, Long> history;
 239  
 
 240  
     /**
 241  
      * The number of elements in the "back" list.
 242  
      */
 243  
     private int backCount;
 244  
 
 245  
     /**
 246  
      * Listeners that are interested when history has changed.
 247  
      */
 248  
     private List<HistoryListener> listeners;
 249  
 }