001package edu.wpi.first.wpilibj.networktables;
002
003import edu.wpi.first.wpilibj.networktables2.*;
004import edu.wpi.first.wpilibj.networktables2.thread.*;
005import edu.wpi.first.wpilibj.networktables2.util.*;
006import edu.wpi.first.wpilibj.networktables2.util.List;
007import edu.wpi.first.wpilibj.tables.*;
008import java.io.*;
009import java.util.*;
010
011/**
012 * 
013 * @author Fredric
014 * @author mwills
015 * 
016 */
017
018public class NetworkTable implements ITable, IRemote {
019        private static final NTThreadManager threadManager = new DefaultThreadManager();
020        
021        /**
022         * The path separator for sub-tables and keys
023         * 
024         */
025        public static final char PATH_SEPARATOR = '/';
026        /**
027         * The default port that network tables operates on
028         */
029        public static final int DEFAULT_PORT = 1735;
030        
031        
032        
033        
034        
035        
036        
037        private static NetworkTableProvider staticProvider = null;
038        
039        private static NetworkTableMode mode = NetworkTableMode.Server;
040        private static int port = DEFAULT_PORT;
041        private static String ipAddress = null;
042        
043        private synchronized static void checkInit(){
044                if(staticProvider!=null)
045                        throw new IllegalStateException("Network tables has already been initialized");
046        }
047        /**
048         * @throws IOException
049         */
050        public synchronized static void initialize() throws IOException {
051                checkInit();
052                staticProvider = new NetworkTableProvider(mode.createNode(ipAddress, port, threadManager));
053        }
054        
055        /**
056         * set the table provider for static network tables methods
057         * This must be called before initalize or getTable
058         */
059        public synchronized static void setTableProvider(NetworkTableProvider provider) {
060                checkInit();
061                staticProvider = provider;
062        }
063        /**
064         * set that network tables should be a server
065         * This must be called before initalize or getTable
066         */
067        public synchronized static void setServerMode(){
068                checkInit();
069                mode = NetworkTableMode.Server;
070        }
071        /**
072         * set that network tables should be a client
073         * This must be called before initalize or getTable
074         */
075        public synchronized static void setClientMode(){
076                checkInit();
077                mode = NetworkTableMode.Client;
078        }
079        
080        /**
081         * set the team the robot is configured for (this will set the ip address that network tables will connect to in client mode)
082         * This must be called before initalize or getTable
083         * @param team the team number
084         */
085        public synchronized static void setTeam(int team){
086                setIPAddress("10." + (team / 100) + "." + (team % 100) + ".2");
087        }
088        /**
089         * @param address the adress that network tables will connect to in client mode
090         */
091        public synchronized static void setIPAddress(final String address){
092                checkInit();
093                ipAddress = address;
094        }
095        /**
096         * Gets the table with the specified key. If the table does not exist, a new table will be created.<br>
097         * This will automatically initialize network tables if it has not been already
098         * 
099         * @param key
100         *            the key name
101         * @return the network table requested
102         */
103        public synchronized static NetworkTable getTable(String key) {
104                if(staticProvider==null)
105                        try {
106                                initialize();
107                        } catch (IOException e) {
108                                throw new RuntimeException("NetworkTable could not be initialized: "+e+": "+e.getMessage());
109                        }
110                return (NetworkTable)staticProvider.getTable(PATH_SEPARATOR+key);
111        }
112        
113
114        
115        private final String path;
116        private final EntryCache entryCache;
117        private final NetworkTableKeyCache absoluteKeyCache;
118        private final NetworkTableProvider provider;
119        private final NetworkTableNode node;
120
121        NetworkTable(String path, NetworkTableProvider provider) {
122                this.path = path;
123                entryCache = new EntryCache(path);
124                absoluteKeyCache = new NetworkTableKeyCache(path);
125                this.provider = provider;
126                node = provider.getNode();
127        }
128        public String toString(){
129                return "NetworkTable: "+path;
130        }
131        
132        public boolean isConnected() {
133                return node.isConnected();
134        }
135
136        public boolean isServer() {
137                return node.isServer();
138        }
139        
140        
141        static class NetworkTableKeyCache extends StringCache{
142                private final String path;
143
144                public NetworkTableKeyCache(String path) {
145                        this.path = path;
146                }
147
148                public String calc(String key) {
149                        return path + PATH_SEPARATOR + key;
150                }
151        }
152        class EntryCache {
153                private final Hashtable cache = new Hashtable();
154                private final String path;
155
156                public EntryCache(String path) {
157                        this.path = path;
158                }
159                
160                public NetworkTableEntry get(final String key){
161                        NetworkTableEntry cachedValue = (NetworkTableEntry)cache.get(key);
162                        if(cachedValue==null){
163                                cachedValue = node.getEntryStore().getEntry(absoluteKeyCache.get(key));
164                                if(cachedValue!=null)
165                                        cache.put(key, cachedValue);
166                        }
167                        return cachedValue;
168                }
169        }
170        
171        
172        private final Hashtable connectionListenerMap = new Hashtable();
173        public void addConnectionListener(IRemoteConnectionListener listener, boolean immediateNotify) {
174                NetworkTableConnectionListenerAdapter adapter = (NetworkTableConnectionListenerAdapter)connectionListenerMap.get(listener);
175                if(adapter!=null)
176                        throw new IllegalStateException("Cannot add the same listener twice");
177                adapter = new NetworkTableConnectionListenerAdapter(this, listener);
178                connectionListenerMap.put(listener, adapter);
179                node.addConnectionListener(adapter, immediateNotify);
180        }
181
182        public void removeConnectionListener(IRemoteConnectionListener listener) {
183                NetworkTableConnectionListenerAdapter adapter = (NetworkTableConnectionListenerAdapter)connectionListenerMap.get(listener);
184                if(adapter!=null)
185                        node.removeConnectionListener(adapter);
186        }
187        
188
189        public void addTableListener(ITableListener listener) {
190                addTableListener(listener, false);
191        }
192
193        private final Hashtable listenerMap = new Hashtable();
194        public void addTableListener(ITableListener listener, boolean immediateNotify) {
195            List adapters = (List)listenerMap.get(listener);
196            if(adapters==null){
197                    adapters = new List();
198                    listenerMap.put(listener, adapters);
199            }
200            NetworkTableListenerAdapter adapter = new NetworkTableListenerAdapter(path+PATH_SEPARATOR, this, listener);
201            adapters.add(adapter);
202            node.addTableListener(adapter, immediateNotify);
203        }
204        public void addTableListener(String key, ITableListener listener, boolean immediateNotify) {
205                List adapters = (List)listenerMap.get(listener);
206                if(adapters==null){
207                        adapters = new List();
208                        listenerMap.put(listener, adapters);
209                }
210                NetworkTableKeyListenerAdapter adapter = new NetworkTableKeyListenerAdapter(key, absoluteKeyCache.get(key), this, listener);
211                adapters.add(adapter);
212                node.addTableListener(adapter, immediateNotify);
213        }
214        public void addSubTableListener(final ITableListener listener) {
215                List adapters = (List)listenerMap.get(listener);
216                if(adapters==null){
217                        adapters = new List();
218                        listenerMap.put(listener, adapters);
219                }
220                NetworkTableSubListenerAdapter adapter = new NetworkTableSubListenerAdapter(path, this, listener);
221                adapters.add(adapter);
222                node.addTableListener(adapter, true);
223        }
224
225        public void removeTableListener(ITableListener listener) {
226                List adapters = (List)listenerMap.get(listener);
227                if(adapters!=null){
228                        for(int i = 0; i<adapters.size(); ++i)
229                                node.removeTableListener((ITableListener) adapters.get(i));
230                        adapters.clear();
231                }
232        }
233        
234        private synchronized NetworkTableEntry getEntry(String key){
235                return entryCache.get(key);
236        }
237
238        /**
239         * Returns the table at the specified key. If there is no table at the
240         * specified key, it will create a new table
241         * 
242         * @param key
243         *            the key name
244         * @return the networktable to be returned
245         */
246        public synchronized ITable getSubTable(String key) {
247                return (NetworkTable)provider.getTable(absoluteKeyCache.get(key));
248        }
249
250        /**
251         * Checks the table and tells if it contains the specified key
252         * 
253         * @param key
254         *            the key to be checked
255         */
256        public boolean containsKey(String key) {
257            return node.containsKey(absoluteKeyCache.get(key));
258        }
259        
260        public boolean containsSubTable(String key){
261            String subtablePrefix = absoluteKeyCache.get(key)+PATH_SEPARATOR;
262            List keys = node.getEntryStore().keys();
263            for(int i = 0; i<keys.size(); ++i){
264                if(((String)keys.get(i)).startsWith(subtablePrefix))
265                    return true;
266            }
267            return false;
268        }
269
270        /**
271         * Maps the specified key to the specified value in this table. The key can
272         * not be null. The value can be retrieved by calling the get method with a
273         * key that is equal to the original key.
274         * 
275         * @param key
276         *            the key
277         * @param value
278         *            the value
279         */
280        public void putNumber(String key, double value) {
281                putValue(key, new Double(value));//TODO cache doubles
282        }
283
284        /**
285         * Returns the key that the name maps to.
286         * 
287         * @param key
288         *            the key name
289         * @return the key
290         * @throws TableKeyNotDefinedException
291         *             if the specified key is null
292         */
293        public double getNumber(String key) throws TableKeyNotDefinedException {
294                return node.getDouble(absoluteKeyCache.get(key));
295        }
296
297        /**
298         * Returns the key that the name maps to. If the key is null, it will return
299         * the default value
300         * 
301         * @param key
302         *            the key name
303         * @param defaultValue
304         *            the default value if the key is null
305         * @return the key
306         */
307        public double getNumber(String key, double defaultValue) {
308                try {
309                        return node.getDouble(absoluteKeyCache.get(key));
310                } catch (TableKeyNotDefinedException e) {
311                        return defaultValue;
312                }
313        }
314
315        /**
316         * Maps the specified key to the specified value in this table. The key can
317         * not be null. The value can be retrieved by calling the get method with a
318         * key that is equal to the original key.
319         * 
320         * @param key
321         *            the key
322         * @param value
323         *            the value
324         */
325        public void putString(String key, String value) {
326                putValue(key, value);
327        }
328
329        /**
330         * Returns the key that the name maps to.
331         * 
332         * @param key
333         *            the key name
334         * @return the key
335         * @throws TableKeyNotDefinedException
336         *             if the specified key is null
337         */
338        public String getString(String key) throws TableKeyNotDefinedException {
339                return node.getString(absoluteKeyCache.get(key));
340        }
341
342        /**
343         * Returns the key that the name maps to. If the key is null, it will return
344         * the default value
345         * 
346         * @param key
347         *            the key name
348         * @param defaultValue
349         *            the default value if the key is null
350         * @return the key
351         */
352        public String getString(String key, String defaultValue) {
353                try {
354                        return node.getString(absoluteKeyCache.get(key));
355                } catch (TableKeyNotDefinedException e) {
356                        return defaultValue;
357                }
358        }
359
360        /**
361         * Maps the specified key to the specified value in this table. The key can
362         * not be null. The value can be retrieved by calling the get method with a
363         * key that is equal to the original key.
364         * 
365         * @param key
366         *            the key
367         * @param value
368         *            the value
369         */
370        public void putBoolean(String key, boolean value) {
371                putValue(key, value?Boolean.TRUE:Boolean.FALSE);
372        }
373
374        /**
375         * Returns the key that the name maps to.
376         * 
377         * @param key
378         *            the key name
379         * @return the key
380         * @throws TableKeyNotDefinedException
381         *             if the specified key is null
382         */
383        public boolean getBoolean(String key) throws TableKeyNotDefinedException {
384                return node.getBoolean(absoluteKeyCache.get(key));
385        }
386
387        /**
388         * Returns the key that the name maps to. If the key is null, it will return
389         * the default value
390         * 
391         * @param key
392         *            the key name
393         * @param defaultValue
394         *            the default value if the key is null
395         * @return the key
396         */
397        public boolean getBoolean(String key, boolean defaultValue) {
398                try {
399                        return node.getBoolean(absoluteKeyCache.get(key));
400                } catch (TableKeyNotDefinedException e) {
401                        return defaultValue;
402                }
403        }
404        
405
406        public void retrieveValue(String key, Object externalValue) {
407            node.retrieveValue(absoluteKeyCache.get(key), externalValue);
408        }
409        
410        /**
411         * Maps the specified key to the specified value in this table. The key can
412         * not be null. The value can be retrieved by calling the get method with a
413         * key that is equal to the original key.
414         * 
415         * @param key the key name
416         * @param value the value to be put
417         */
418        public void putValue(String key, Object value){
419                NetworkTableEntry entry = entryCache.get(key);
420                if(entry!=null)
421                        node.putValue(entry, value);
422                else
423                        node.putValue(absoluteKeyCache.get(key), value);
424        }
425        
426        /**
427         * Returns the key that the name maps to.
428         * NOTE: If the value is a double, it will return a Double object,
429         * not a primitive.  To get the primitive, use getDouble
430         * 
431         * @param key
432         *            the key name
433         * @return the key
434         * @throws TableKeyNotDefinedException
435         *             if the specified key is null
436         */
437        public Object getValue(String key) throws TableKeyNotDefinedException {
438                return node.getValue(absoluteKeyCache.get(key));
439        }
440        
441        /**
442         * Returns the key that the name maps to. If the key is null, it will return
443         * the default value
444         * NOTE: If the value is a double, it will return a Double object,
445         * not a primitive.  To get the primitive, use getDouble
446         * 
447         * @param key
448         *            the key name
449         * @param defaultValue
450         *            the default value if the key is null
451         * @return the key
452         */
453        public Object getValue(String key, Object defaultValue) {
454                try {
455                        return node.getValue(absoluteKeyCache.get(key));
456                } catch(TableKeyNotDefinedException e){
457                        return defaultValue;
458                }
459        }
460        
461        
462        
463        
464        
465        
466        
467        
468        
469        
470        
471        
472        
473        
474        
475        
476        
477        
478        
479        
480        
481
482     /*
483      * Depricated Methods
484      */
485     /**
486      * @deprecated 
487     * Maps the specified key to the specified value in this table.
488     * The key can not be null.
489     * The value can be retrieved by calling the get method with a key that is equal to the original key.
490     * @param key the key
491     * @param value the value
492     * @throws IllegalArgumentException if key is null
493     */
494    public void putInt(String key, int value) {
495        putNumber(key, value);
496    }
497
498    /**
499     * @deprecated 
500     * Returns the value at the specified key.
501     * @param key the key
502     * @return the value
503     * @throws TableKeyNotDefinedException if there is no value mapped to by the key
504     * @throws IllegalArgumentException if the value mapped to by the key is not an int
505     * @throws IllegalArgumentException if the key is null
506     */
507    public int getInt(String key) throws TableKeyNotDefinedException{
508        return (int) getNumber(key);
509    }
510
511    /**
512     * @deprecated 
513     * Returns the value at the specified key.
514     * @param key the key
515     * @param defaultValue the value returned if the key is undefined
516     * @return the value
517     * @throws NetworkTableKeyNotDefined if there is no value mapped to by the key
518     * @throws IllegalArgumentException if the value mapped to by the key is not an int
519     * @throws IllegalArgumentException if the key is null
520     */
521    public int getInt(String key, int defaultValue) throws TableKeyNotDefinedException{
522        try {
523            return (int) getNumber(key);
524        } catch (NoSuchElementException ex) {
525            return defaultValue;
526        }
527    }
528
529    /**
530     * @deprecated 
531     * Maps the specified key to the specified value in this table.
532     * The key can not be null.
533     * The value can be retrieved by calling the get method with a key that is equal to the original key.
534     * @param key the key
535     * @param value the value
536     * @throws IllegalArgumentException if key is null
537     */
538    public void putDouble(String key, double value) {
539        putNumber(key, value);
540    }
541
542    /**
543     * @deprecated 
544     * Returns the value at the specified key.
545     * @param key the key
546     * @return the value
547     * @throws NoSuchEleNetworkTableKeyNotDefinedmentException if there is no value mapped to by the key
548     * @throws IllegalArgumentException if the value mapped to by the key is not a double
549     * @throws IllegalArgumentException if the key is null
550     */
551    public double getDouble(String key) throws TableKeyNotDefinedException{
552        return getNumber(key);
553    }
554
555    /**
556     * @deprecated 
557     * Returns the value at the specified key.
558     * @param key the key
559     * @param defaultValue the value returned if the key is undefined
560     * @return the value
561     * @throws NoSuchEleNetworkTableKeyNotDefinedmentException if there is no value mapped to by the key
562     * @throws IllegalArgumentException if the value mapped to by the key is not a double
563     * @throws IllegalArgumentException if the key is null
564     */
565    public double getDouble(String key, double defaultValue) {
566        return getNumber(key, defaultValue);
567    }
568}