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}