package org.yamcs.parameter; import java.util.Collection; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.function.Consumer; import org.yamcs.parameter.ParameterValue; import org.yamcs.xtce.Parameter; /** * * Stores a collection of ParameterValue indexed on Parameter * * It works like a LinkedHashMap<Parameter, LinkedList<ParameterValue>> * * * Not thread safe * @author nm * */ public class ParameterValueList implements Collection<ParameterValue> { Entry[] table; Entry head; int size; int threshold; float loadFactor = 0.75f; public ParameterValueList() { size = 0; table = new Entry[16]; threshold = (int)(table.length*loadFactor); initHead(); } /** * @param pvs */ public ParameterValueList(Collection<ParameterValue> pvs) { int len = (int)(pvs.size()/loadFactor)+ 1; len = roundUpToPowerOfTwo(len); table = new Entry[len]; threshold = (int)(len * loadFactor); size = 0; initHead(); for(ParameterValue pv:pvs) { doAdd(pv); } } //used for unit tests to ensure max collision ParameterValueList(int capacity, Collection<ParameterValue> pvs) { int len = roundUpToPowerOfTwo(capacity); table = new Entry[len]; threshold = (int)(len * loadFactor); size = 0; initHead(); for(ParameterValue pv:pvs) { doAdd(pv); } } private void initHead() { head = new Entry(null); head.before = head.after = head; } @Override public boolean add(ParameterValue pv) { if(pv==null) throw new NullPointerException(); if(size-1 >= threshold) { ensureCapacity(2*table.length); threshold = 2*threshold; } doAdd(pv); return true; } /** * Return the number of values for p * @param p * @return */ public int count(Parameter p) { int hash = getHash(p); int index = hash & (table.length - 1); Entry e = table[index]; int count = 0; while(e!=null) { if(e.pv.getParameter()==p) { count++; } e = e.next; } return count; } private void ensureCapacity(int newCapacity) { Entry[] oldt = table; Entry[] newt = new Entry[newCapacity]; //transfer content for(int i = 0; i<oldt.length; i++) { Entry e = oldt[i]; while(e!=null) { int hash = getHash(e.pv.getParameter()); int index = hash & (newt.length - 1); Entry next = e.next; e.next = null; Entry e1 = newt[index]; if(e1 == null) { newt[index] = e; } else { while(e1.next!=null) e1=e1.next; e1.next = e; } e = next; } } table = newt; } /** * add a parameter to the hashtable, to the end of the list for the same parameter * * @param pv */ private void doAdd(ParameterValue pv) { Entry newEntry = new Entry(pv); Entry[] t = table; int hash = getHash(pv.getParameter()); int index = hash & (t.length - 1); if(t[index] == null) { t[index] = newEntry; } else { Entry e = t[index]; while(e.next!=null) e=e.next; e.next = newEntry; } newEntry.after = head; newEntry.before = head.before; head.before.after = newEntry; head.before = newEntry; size++; } private int getHash(Parameter p) { return p.hashCode(); } public int getSize() { return size; } /** * Returns the last inserted value for Parameter p or null if there is no value * @param p * @return */ public ParameterValue getLastInserted(Parameter p) { int index = getHash(p) & (table.length - 1); ParameterValue r = null; for(Entry e = table[index] ; e!=null; e=e.next) { if(e.pv.getParameter()==p) { r = e.pv; } } return r; } public ParameterValue getFirstInserted(Parameter p) { int index = getHash(p) & (table.length - 1); ParameterValue r = null; for(Entry e = table[index] ; e!=null; e=e.next) { if(e.pv.getParameter()==p) { r = e.pv; break; } } return r; } /** * Performs the given action for each value of the parameter p * The values are considered in insertion order - oldest is first to be processed * @param p * @param action */ public void forEach(Parameter p, Consumer<ParameterValue> action) { int index = getHash(p) & (table.length - 1); for(Entry e = table[index] ; e!=null; e=e.next) { if(e.pv.getParameter()==p) { action.accept(e.pv); } } } /** * Remove the last inserted value for Parameter p * * @param p * @return the value removed or null if there was no value for p */ public ParameterValue removeLast(Parameter p) { int index = getHash(p) & (table.length - 1); Entry e = table[index]; if(e == null) return null; Entry prev_r = null; Entry prev_e = null; Entry r = null; while(e!=null) { if(e.pv.getParameter()==p) { prev_r = prev_e; r = e; } prev_e = e; e = e.next; } if(r==null) { return null; } size--; if(table[index]==r) { table[index] = r.next; } else { prev_r.next = r.next; } removeEntryFromLinkedList(r); return r.pv; } private void removeEntryFromLinkedList(Entry r) { Entry b = r.before; Entry a = r.after; b.after = a; a.before = b; } /** * Remove the first inserted value for Parameter p * * @param p * @return the value removed or null if there was no value for p */ public ParameterValue removeFirst(Parameter p) { int index = getHash(p) & (table.length - 1); Entry prev = table[index]; if(prev == null) return null; Entry e = prev; Entry r = null; while(e!=null) { if(e.pv.getParameter()==p) { r = e; break; } prev = e; e = e.next; } if(r==null) { return null; } size--; if(table[index]==r) { table[index] = r.next; } else { prev.next = r.next; } removeEntryFromLinkedList(r); return r.pv; } /** * this is copied from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 * * */ static int roundUpToPowerOfTwo(int v){ v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } @Override public Iterator<ParameterValue> iterator() { return new Iter(); } /** * Adds all element to this collection * @param c * @return */ @Override public boolean addAll(Collection<? extends ParameterValue> c) { int newSize = size+c.size(); if(newSize>threshold) { int newCapacity = roundUpToPowerOfTwo(newSize); ensureCapacity(newCapacity); threshold = (int) (newCapacity*loadFactor); } for(ParameterValue pv:c) { doAdd(pv); } return false; } /** * Throws UnsupportedOperationException */ @Override public void clear() { throw new UnsupportedOperationException(); } /** * Return true if the list contains the exact same ParameterValue. * That means exactly the same object. * * @param o * @return */ @Override public boolean contains(Object o) { if(!(o instanceof ParameterValue)) return false; ParameterValue pv = (ParameterValue)o; int index = getHash(pv.getParameter()) & (table.length - 1); for(Entry e = table[index] ; e!=null; e=e.next) { if(e.pv==pv) { return true; } } return false; } /** * Throws UnsupportedOperationException */ @Override public boolean containsAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean isEmpty() { return size == 0; } /** * Throws UnsupportedOperationException */ @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } /** * Throws UnsupportedOperationException */ @Override public boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException(); } /** * Throws UnsupportedOperationException */ @Override public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public int size() { return size; } /** * Returns a copy of the list as array */ @Override public Object[] toArray() { ParameterValue[] r = new ParameterValue[size]; int i=0; Iterator<ParameterValue> it = iterator(); while(it.hasNext()) { r[i++] = it.next(); } return r; } /** * Throws UnsupportedOperationException */ @Override public <T> T[] toArray(T[] a) { throw new UnsupportedOperationException(); } public String toString() { StringBuilder sb = new StringBuilder(); sb.append("["); boolean first = true; for(ParameterValue pv:this) { if(first) { first = false; } else { sb.append(", "); } sb.append(pv.toString()); } sb.append("]"); return sb.toString(); } static class Entry { final ParameterValue pv; Entry next, before, after; Entry(ParameterValue pv) { this.pv = pv; } } private final class Iter implements Iterator<ParameterValue> { Entry next = head.after; @Override public boolean hasNext() { return next!=head; } @Override public ParameterValue next() { if(next==head) throw new NoSuchElementException(); Entry r = next; next = r.after; return r.pv; } @Override public void remove() { throw new UnsupportedOperationException(); } } }