001package edu.wpi.first.wpilibj.networktables2; 002 003import edu.wpi.first.wpilibj.networktables2.type.*; 004import edu.wpi.first.wpilibj.networktables2.util.*; 005import edu.wpi.first.wpilibj.tables.*; 006import java.util.Enumeration; 007import java.util.Hashtable; 008 009/** 010 * An entry store that handles storing entries and applying transactions 011 * 012 * @author mwills 013 * @author Fredric 014 * 015 */ 016 017public abstract class AbstractNetworkTableEntryStore implements IncomingEntryReceiver{ 018 protected final CharacterArrayMap idEntries = new CharacterArrayMap(); 019 protected final Hashtable namedEntries = new Hashtable(); 020 021 protected final TableListenerManager listenerManager; 022 023 protected AbstractNetworkTableEntryStore(TableListenerManager listenerManager){ 024 this.listenerManager = listenerManager; 025 } 026 027 /** 028 * Get an entry based on it's id 029 * @param entryId the id f the entry to look for 030 * @return the entry or null if the entry does not exist 031 */ 032 public NetworkTableEntry getEntry(final char entryId){ 033 synchronized(this){ 034 return (NetworkTableEntry) idEntries.get(entryId); 035 } 036 } 037 /** 038 * Get an entry based on it's name 039 * @param name the name of the entry to look for 040 * @return the entry or null if the entry does not exist 041 */ 042 public NetworkTableEntry getEntry(String name){ 043 synchronized(this){ 044 return (NetworkTableEntry) namedEntries.get(name); 045 } 046 } 047 /** 048 * Get an entry based on it's name 049 * @param name the name of the entry to look for 050 * @return the entry or null if the entry does not exist 051 */ 052 public List keys(){ 053 synchronized(this){ 054 List entryKeys = new List(); 055 Enumeration e = namedEntries.keys(); 056 while(e.hasMoreElements()) 057 entryKeys.add(e.nextElement()); 058 return entryKeys; 059 } 060 } 061 062 /** 063 * Remove all entries 064 * NOTE: This method should not be used with applications which cache entries which would lead to unknown results 065 * This method is for use in testing only 066 */ 067 public void clearEntries() { 068 synchronized (this) { 069 idEntries.clear(); 070 namedEntries.clear(); 071 } 072 } 073 074 /** 075 * clear the id's of all entries 076 */ 077 public void clearIds() { 078 synchronized(this){ 079 idEntries.clear(); 080 Enumeration e = namedEntries.elements(); 081 while(e.hasMoreElements()) 082 ((NetworkTableEntry)e.nextElement()).clearId(); 083 } 084 } 085 086 087 protected OutgoingEntryReceiver outgoingReceiver; 088 protected OutgoingEntryReceiver incomingReceiver; 089 public void setOutgoingReceiver(final OutgoingEntryReceiver receiver){ 090 outgoingReceiver = receiver; 091 } 092 public void setIncomingReceiver(OutgoingEntryReceiver receiver){ 093 incomingReceiver = receiver; 094 } 095 096 protected abstract boolean addEntry(NetworkTableEntry entry); 097 protected abstract boolean updateEntry(NetworkTableEntry entry, char sequenceNumber, Object value); 098 099 /** 100 * Check if two objects are equal doing a deep equals of arrays 101 * This method assumes that o1 and o2 are of the same type (if one is an object array the other one is also) 102 * @param o1 103 * @param o2 104 */ 105 private static boolean valuesEqual(Object o1, Object o2){ 106 if(o1 instanceof Object[]){ 107 Object[] a1 = (Object[])o1; 108 Object[] a2 = (Object[])o2; 109 if(a1.length!=a2.length) 110 return false; 111 for(int i = 0; i<a1.length; ++i) 112 if(!valuesEqual(a1[i], a2[i])) 113 return false; 114 return true; 115 } 116 return o1!=null?o1.equals(o2):o2==null; 117 } 118 119 /** 120 * Stores the given value under the given name and queues it for 121 * transmission to the server. 122 * 123 * @param name The name under which to store the given value. 124 * @param type The type of the given value. 125 * @param value The value to store. 126 * @throws TableKeyExistsWithDifferentTypeException Thrown if an 127 * entry already exists with the given name and is of a different type. 128 */ 129 public void putOutgoing(String name, NetworkTableEntryType type, Object value) throws TableKeyExistsWithDifferentTypeException{ 130 synchronized(this){ 131 NetworkTableEntry tableEntry = (NetworkTableEntry)namedEntries.get(name); 132 if(tableEntry==null){ 133 //TODO validate type 134 tableEntry = new NetworkTableEntry(name, type, value); 135 if(addEntry(tableEntry)){ 136 tableEntry.fireListener(listenerManager); 137 outgoingReceiver.offerOutgoingAssignment(tableEntry); 138 } 139 } 140 else{ 141 if(tableEntry.getType().id != type.id) 142 throw new edu.wpi.first.wpilibj.networktables2.TableKeyExistsWithDifferentTypeException(name, tableEntry.getType()); 143 if(!valuesEqual(value, tableEntry.getValue())){ 144 if(updateEntry(tableEntry, (char)(tableEntry.getSequenceNumber()+1), value)){ 145 outgoingReceiver.offerOutgoingUpdate(tableEntry); 146 } 147 tableEntry.fireListener(listenerManager); 148 } 149 } 150 } 151 } 152 153 public void putOutgoing(NetworkTableEntry tableEntry, Object value){ 154 synchronized(this){ 155 //TODO Validate type 156 if(!valuesEqual(value, tableEntry.getValue())){ 157 if(updateEntry(tableEntry, (char)(tableEntry.getSequenceNumber()+1), value)){ 158 outgoingReceiver.offerOutgoingUpdate(tableEntry); 159 } 160 tableEntry.fireListener(listenerManager); 161 } 162 } 163 } 164 165 166 public void offerIncomingAssignment(NetworkTableEntry entry) { 167 synchronized(this){ 168 NetworkTableEntry tableEntry = (NetworkTableEntry)namedEntries.get(entry.name); 169 if(addEntry(entry)){ 170 if(tableEntry==null) 171 tableEntry = entry; 172 tableEntry.fireListener(listenerManager); 173 incomingReceiver.offerOutgoingAssignment(tableEntry); 174 } 175 } 176 } 177 178 179 public void offerIncomingUpdate(NetworkTableEntry entry, char sequenceNumber, Object value) { 180 synchronized(this){ 181 if(updateEntry(entry, sequenceNumber, value)){ 182 entry.fireListener(listenerManager); 183 incomingReceiver.offerOutgoingUpdate(entry); 184 } 185 } 186 } 187 188 189 /** 190 * Called to say that a listener should notify the listener manager of all of the entries 191 * @param listener 192 * @param table 193 */ 194 public void notifyEntries(final ITable table, final ITableListener listener) { 195 synchronized(this){ 196 Enumeration entryIterator = namedEntries.elements(); 197 while(entryIterator.hasMoreElements()){ 198 NetworkTableEntry entry = (NetworkTableEntry) entryIterator.nextElement(); 199 listener.valueChanged(table, entry.name, entry.getValue(), true); 200 } 201 } 202 } 203 204 205 206 207 208 209 /** 210 * An object that handles firing Table Listeners 211 * @author Mitchell 212 * 213 */ 214 public interface TableListenerManager { 215 /** 216 * Called when the object should fire it's listeners 217 * @param key 218 * @param value 219 * @param isNew 220 */ 221 void fireTableListeners(String key, Object value, boolean isNew); 222 } 223}