Coverage Report - com.nettgryppa.util.InfiniteList
 
Classes in this File Line Coverage Branch Coverage Complexity
InfiniteList
99%
152/154
100%
44/44
0
InfiniteList$InfiniteListIterator
100%
42/42
100%
6/6
0
 
 1  
 package com.nettgryppa.util;
 2  
 
 3  
 // Copyright 2006 Gregory Rubin grrubin@gmail.com
 4  
 //  Permission is given to use, modify, and or distribute this code so long as this message remains attached
 5  
 
 6  
 import java.util.*;
 7  
 
 8  
 /**
 9  
  * This class is meant to simulate a list of infinite (and optionally bi-infinite) length.  All positive indices
 10  
  * are valid but will default to <code>null</code>.  This list can also be bi-infinite, in which case all 
 11  
  * negative indices are also valid. This class is also useful for sparse lists as it is backed by a {@link java.util.SortedMap}
 12  
  * Copyright 2006 Gregory Rubin <a href="mailto:grrubin@gmail.com">grrubin@gmail.com</a><br>
 13  
  *  Permission is given to use, modify, and or distribute this code so long as this message remains attached<br>
 14  
  * @author grrubin@gmail.com
 15  
  * @version 1.0
 16  
  */
 17  0
 public class InfiniteList<E> implements List<E> {
 18  
   private SortedMap<Integer, E> myMap;
 19  
   private boolean allowNegative;
 20  
   
 21  
   /**
 22  
    * Constructs an empty list with <code>allowNegative</code> set to false.
 23  
    */
 24  8
   public InfiniteList() {
 25  8
     myMap = new TreeMap<Integer, E>();
 26  8
     allowNegative = false;
 27  8
   }
 28  
   
 29  
   /**
 30  
    * Constructs an empty list.
 31  
    */
 32  48
   public InfiniteList(boolean allowNegative) {
 33  48
     myMap = new TreeMap<Integer, E>();
 34  48
     this.allowNegative = allowNegative;
 35  48
   }
 36  
   
 37  
   /**
 38  
    * Copy constructor
 39  
    */
 40  2
   public InfiniteList(InfiniteList<E> obj) {
 41  2
     myMap = new TreeMap<Integer, E>(obj.myMap);
 42  
     
 43  2
     this.allowNegative = obj.allowNegative;
 44  2
   }
 45  
   
 46  
   /**
 47  
    * Does this specific instance allow negative indices
 48  
    */
 49  
   public boolean allowsNegative() {
 50  2
     return allowNegative;
 51  
   }
 52  
   
 53  
   public int size() {
 54  33
     if(myMap.isEmpty())
 55  13
       return 0;
 56  20
     return (myMap.lastKey().intValue() - myMap.firstKey().intValue() + 1);
 57  
   }
 58  
 
 59  
   /**
 60  
    * Maximum assigned index
 61  
    */
 62  
   public int max() {
 63  234
     if(myMap.isEmpty() || null == myMap.lastKey()) {
 64  20
       return 0;
 65  
     } else {
 66  214
       return (myMap.lastKey().intValue());
 67  
     }
 68  
   }
 69  
   
 70  
   /**
 71  
    * Minimum assigned index
 72  
    */
 73  
   public int min() {
 74  82
     if(myMap.isEmpty() || null == myMap.firstKey()) {
 75  18
       return 0;
 76  
     } else {
 77  64
       return (myMap.firstKey().intValue());
 78  
     }
 79  
   }
 80  
   
 81  
   /*
 82  
    * How many indices have non-null values
 83  
    */
 84  
   public int load() {
 85  31
     return myMap.size();
 86  
   }
 87  
 
 88  
   public boolean add(E element) {
 89  47
     if(null == element)
 90  1
       throw new NullPointerException("Adding a null element to the end of the list is meaningless.");
 91  
       
 92  46
     if(isEmpty())
 93  25
       set(0, element);
 94  
     else
 95  21
       add(max() + 1, element);
 96  
       
 97  46
     return true;
 98  
   }
 99  
   
 100  
   public void add(int index, E element) {
 101  26
     if(null == element && index > max())
 102  1
       throw new NullPointerException("Adding a null element to the end of the list is meaningless.");
 103  
   
 104  25
     if(index <= max()) {
 105  4
       Integer[] indices = myMap.tailMap(Integer.valueOf(index)).keySet().toArray(new Integer[0]);
 106  4
       Arrays.sort(indices);
 107  7
       for(int x = indices.length - 1; x >= 0; x--) {
 108  3
         set(indices[x] + 1, get(indices[x]));
 109  3
         set(indices[x], null);
 110  
       }
 111  
     }
 112  25
     set(index, element);
 113  24
   }
 114  
 
 115  
   /*
 116  
    * Add all elements in the collection to this list.
 117  
    * This will handle nulls as expected in that if they come before the last element,
 118  
    * they will be preserved.
 119  
    */
 120  
   public boolean addAll(Collection<? extends E> c) {
 121  4
     if(!isEmpty())
 122  1
       return addAll(max() + 1, c);
 123  
     else
 124  3
       return addAll(0, c);
 125  
   }
 126  
   
 127  
   /*
 128  
    * Add all elements in the collection to this list starting at <code>index</code>.
 129  
    * This will handle nulls as expected in that if they come before the last element,
 130  
    * they will be preserved.
 131  
    */
 132  
   public boolean addAll(int index, Collection<? extends E> c) {
 133  7
     if(c.isEmpty())
 134  1
       return false;
 135  
       
 136  6
     if(index <= max() && !isEmpty()) {
 137  1
       Integer[] indices = myMap.tailMap(Integer.valueOf(index)).keySet().toArray(new Integer[0]);
 138  1
       Arrays.sort(indices);
 139  3
       for(int x = indices.length - 1; x >= 0; x--) {
 140  2
         set(indices[x] + c.size(), get(indices[x]));
 141  2
         set(indices[x], null);
 142  
       }
 143  
     }
 144  
     
 145  6
     for(E obj: c) {
 146  13
       set(index, obj);
 147  13
       index++;
 148  13
     }
 149  
     
 150  6
     return true;
 151  
   }
 152  
   
 153  
   public void clear() {
 154  12
     myMap.clear();
 155  12
   }
 156  
   
 157  
   public boolean contains(Object o) {
 158  41
     if(null == o)
 159  9
       return true;
 160  
       
 161  32
     return myMap.containsValue(o);
 162  
   }
 163  
   
 164  
   public boolean containsAll(Collection<?> o) {
 165  13
     for(Object obj: o) {
 166  32
       if(!contains(obj))
 167  2
         return false;
 168  30
     }
 169  10
     return true;
 170  
   }
 171  
   
 172  
   /**
 173  
    * All indices with non-null values in ascending order
 174  
    */
 175  
   public Set<Integer> indexSet() {
 176  2
     return myMap.keySet();
 177  
   }
 178  
   
 179  
   /**
 180  
    * Acts as expected if other object is an InfiniteList.  Else returns true if
 181  
    * this list has no negative elements and its positive elements are equal to the list
 182  
    * passed in.
 183  
    * NOTE: If the other object is not an InfiniteList, we cannot guarantee that 
 184  
    * <code>infiniteList.equals(o) == o.equals(infiniteList)</code>  An example of where this will
 185  
    * break is any list that contains nulls on either end.
 186  
    */
 187  
   public boolean equals(Object o) {
 188  24
     if(null == o) {
 189  1
       return false;
 190  23
     } else if(o instanceof InfiniteList) {
 191  15
       return ((InfiniteList)o).myMap.equals(myMap);
 192  8
     } else if(o instanceof List) {
 193  7
       List tempList = (List)o;
 194  7
       if(isEmpty() && tempList.isEmpty()) {
 195  1
         return true;
 196  6
       } else if(min() >= 0 && max() < tempList.size()) {
 197  12
         for(int x = 0; x < tempList.size(); x++) {
 198  9
           if ( (get(x) != null && !get(x).equals(tempList.get(x)))
 199  
               || (tempList.get(x) != null && !tempList.get(x).equals(get(x))) )
 200  1
             return false;
 201  
         }
 202  3
         return true;
 203  
       } else {
 204  2
         return false;
 205  
       }
 206  
     } else {
 207  1
       return false;
 208  
     }
 209  
   }
 210  
   
 211  
   public E get(int index) {
 212  260
     if(!allowNegative && index < 0)
 213  1
       throw new IndexOutOfBoundsException();
 214  259
     return myMap.get(Integer.valueOf(index));
 215  
   }
 216  
   
 217  
   public int hashCode() { // TODO: Improve
 218  7
     int hashCode = 1;
 219  7
     ListIterator i = listIterator(0);
 220  23
     while (i.hasNext()) {
 221  16
         Object obj = i.next();
 222  16
         hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
 223  16
     }
 224  7
     i = listIterator(0);
 225  8
     while (i.hasPrevious()) {
 226  1
         Object obj = i.previous();
 227  1
         hashCode = 37*hashCode + (obj==null ? 0 : obj.hashCode());
 228  1
     }
 229  
     
 230  7
     return hashCode;
 231  
   }
 232  
   
 233  
   /**
 234  
    * WARNING! DO NOT USE THIS TO DETERMINE IF THE LIST CONTAINS AN ELEMENT AS 
 235  
    * -1 IS A VALID RESPONSE! USE {@link #contains(Object o)}
 236  
    * If <code>o</code> is <code>null</code> then returns {@link #min()} - 1
 237  
    */
 238  
   public int indexOf(Object o) {
 239  8
     if(null == o) {
 240  1
       return min() - 1;
 241  7
     } else if(!myMap.containsValue(o)) {
 242  1
       return -1;
 243  
     } else {
 244  6
       for(Map.Entry<Integer, E> entry: myMap.entrySet()) {
 245  16
         if(entry.getValue().equals(o))
 246  6
           return entry.getKey().intValue();
 247  10
       }
 248  0
       return -1; // Unreachable line
 249  
     }
 250  
   }
 251  
   
 252  
   public boolean isEmpty() {
 253  206
     return myMap.isEmpty();
 254  
   }
 255  
   
 256  
   /**
 257  
    * WARNING! DO NOT USE THIS TO DETERMINE IF THE LIST CONTAINS AN ELEMENT AS 
 258  
    * -1 IS A VALID RESPONSE! USE {@link #contains(Object o)}
 259  
    * WARNING! THIS IS A SLOW IMPLEMENTATION
 260  
    * If <code>o</code> is <code>null</code> then returns {@link #max()} + 1
 261  
    */
 262  
   public int lastIndexOf(Object o) {
 263  5
     if(null == o) {
 264  1
       return max() + 1;
 265  4
     } else if(!myMap.containsValue(o)) {
 266  1
       return -1;
 267  
     } else {
 268  3
       int result = -1;
 269  3
       for(Map.Entry<Integer, E> entry: myMap.entrySet()) {
 270  18
         if(entry.getValue().equals(o))
 271  4
           result = entry.getKey().intValue();
 272  18
       }
 273  3
       return result;
 274  
     }
 275  
   }
 276  
   
 277  
   public Iterator<E> iterator() {
 278  5
     return listIterator();
 279  
   }
 280  
   
 281  
   public ListIterator<E> listIterator() {
 282  18
     return new InfiniteListIterator<E>(this);
 283  
   }
 284  
   
 285  
   public ListIterator<E> listIterator(int index) {
 286  15
     return new InfiniteListIterator<E>(this, index);
 287  
   }
 288  
   
 289  
   public E remove(int index) {
 290  6
     E old = get(index);
 291  6
     set(index, null);
 292  6
     Set<Integer> indices = new TreeSet<Integer>(myMap.tailMap(Integer.valueOf(index)).keySet());
 293  6
     for(Integer x: indices) {
 294  6
       set(x.intValue() - 1, get(x.intValue()));
 295  6
       set(x.intValue(), null);
 296  6
     }
 297  6
     return old;
 298  
   }
 299  
   
 300  
   public boolean remove(Object o) {
 301  9
     if(null == o)
 302  5
       return true; // We always contain the null element
 303  
     
 304  4
     if(contains(o)) {
 305  3
       int index = indexOf(o);
 306  3
       remove(index);
 307  3
       return true;
 308  
     } else {
 309  1
       return false;
 310  
     }
 311  
   }
 312  
   
 313  
   public E set(int index, E element) {
 314  174
     if(!allowNegative && index < 0)
 315  2
       throw new IndexOutOfBoundsException();
 316  
       
 317  172
     if(null != element)
 318  147
       return myMap.put(Integer.valueOf(index), element);
 319  
     else
 320  25
       return myMap.remove(Integer.valueOf(index));
 321  
   }
 322  
   
 323  
   public InfiniteList<E> subList(int fromIndex, int toIndex) {
 324  4
     InfiniteList<E> result = new InfiniteList<E>();
 325  4
     result.myMap = myMap.subMap(Integer.valueOf(fromIndex), Integer.valueOf(toIndex));
 326  4
     result.allowNegative = allowNegative;
 327  
     
 328  4
     return result;
 329  
   }
 330  
  
 331  
   public boolean retainAll(Collection<?> c) {
 332  3
     return myMap.values().retainAll(c);
 333  
   }
 334  
   
 335  
   public boolean removeAll(Collection<?> c) {
 336  3
     return myMap.values().removeAll(c);
 337  
   }
 338  
  
 339  
   /**
 340  
    * NOTE: While order is guaranteed, indices are not guarenteed to be identical unless all are non-negative
 341  
    */
 342  
   public Object[] toArray() {
 343  4
     return toArray(new Object[0]);
 344  
   }
 345  
   
 346  
   /**
 347  
    * NOTE: While order is guaranteed, indices are not guarenteed to be identical unless all are non-negative
 348  
    */
 349  
   @SuppressWarnings("unchecked")
 350  
   public<T> T[] toArray(T[] a) {
 351  13
     Arrays.fill(a, null);
 352  
     
 353  13
     int offset = (min() < 0 ?
 354  
                   -1 * min() :
 355  
                   0);
 356  
 
 357  
     // We mock this up by building a normal list and sending that back
 358  13
     List<T> tempList = new ArrayList<T>(max() + offset);
 359  13
     if(!isEmpty()) {
 360  48
       for(int x = 0; x <= max() + offset; x++) {
 361  39
         tempList.add((T)get(x - offset));
 362  
       }
 363  
     }
 364  
     
 365  13
     return (T[])tempList.toArray(a);
 366  
   }
 367  
  
 368  
   private static class InfiniteListIterator<E> implements ListIterator<E> {
 369  33
     private boolean tainted = true;
 370  
     private InfiniteList<E> myList;
 371  
     private int nextIndex;
 372  
     private int previousIndex;
 373  
     private int lastIndex;
 374  
     
 375  18
     public InfiniteListIterator(InfiniteList<E> list) {
 376  18
       myList = list;
 377  18
       nextIndex = list.min();
 378  18
       previousIndex = nextIndex - 1;
 379  18
     }
 380  
     
 381  15
     public InfiniteListIterator(InfiniteList<E> list, int index) {
 382  15
       myList = list;
 383  15
       nextIndex = index;
 384  15
       previousIndex = nextIndex - 1;
 385  15
     }
 386  
     
 387  
     public boolean hasNext() {
 388  92
       if(myList.isEmpty())
 389  5
         return false;
 390  87
       return myList.max() >= nextIndex;
 391  
     }
 392  
     
 393  
     public boolean hasPrevious() {
 394  18
       if(myList.isEmpty())
 395  3
         return false;
 396  15
       return myList.min() <= previousIndex;
 397  
     }
 398  
     
 399  
     public E next() {
 400  66
       tainted = false;
 401  66
       previousIndex = nextIndex;
 402  66
       lastIndex = nextIndex;
 403  66
       return myList.get(nextIndex++);
 404  
     }
 405  
     
 406  
     public E previous() {
 407  10
       tainted = false;
 408  10
       nextIndex = previousIndex;
 409  10
       lastIndex = previousIndex;
 410  10
       return myList.get(previousIndex--);
 411  
     }
 412  
     
 413  
     public int nextIndex() {
 414  12
       return nextIndex;
 415  
     }
 416  
     
 417  
     public int previousIndex() {
 418  7
       return previousIndex;
 419  
     }
 420  
 
 421  
     public void remove() {
 422  3
       if(tainted)
 423  1
         throw new IllegalStateException();
 424  2
       tainted = true;
 425  2
       myList.remove(lastIndex);
 426  2
       nextIndex--;
 427  2
     }
 428  
     
 429  
     public void add(E o) {
 430  2
       myList.add(nextIndex, o);
 431  2
       nextIndex++;
 432  2
       previousIndex = nextIndex - 1;
 433  2
       tainted = true;
 434  2
     }
 435  
     
 436  
     public void set(E o) {
 437  2
       if(tainted)
 438  1
         throw new IllegalStateException();
 439  
         
 440  1
       myList.set(lastIndex, o);
 441  1
     }
 442  
   };
 443  
 }