mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-21 01:01:43 +00:00
Initial checkin of unified hierarchy of WPILib 2015
This commit is contained in:
@@ -0,0 +1,568 @@
|
||||
package edu.wpi.first.wpilibj.networktables;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.thread.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.util.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.util.List;
|
||||
import edu.wpi.first.wpilibj.tables.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Fredric
|
||||
* @author mwills
|
||||
*
|
||||
*/
|
||||
|
||||
public class NetworkTable implements ITable, IRemote {
|
||||
private static final NTThreadManager threadManager = new DefaultThreadManager();
|
||||
|
||||
/**
|
||||
* The path separator for sub-tables and keys
|
||||
*
|
||||
*/
|
||||
public static final char PATH_SEPARATOR = '/';
|
||||
/**
|
||||
* The default port that network tables operates on
|
||||
*/
|
||||
public static final int DEFAULT_PORT = 1735;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private static NetworkTableProvider staticProvider = null;
|
||||
|
||||
private static NetworkTableMode mode = NetworkTableMode.Server;
|
||||
private static int port = DEFAULT_PORT;
|
||||
private static String ipAddress = null;
|
||||
|
||||
private synchronized static void checkInit(){
|
||||
if(staticProvider!=null)
|
||||
throw new IllegalStateException("Network tables has already been initialized");
|
||||
}
|
||||
/**
|
||||
* @throws IOException
|
||||
*/
|
||||
public synchronized static void initialize() throws IOException {
|
||||
checkInit();
|
||||
staticProvider = new NetworkTableProvider(mode.createNode(ipAddress, port, threadManager));
|
||||
}
|
||||
|
||||
/**
|
||||
* set the table provider for static network tables methods
|
||||
* This must be called before initalize or getTable
|
||||
*/
|
||||
public synchronized static void setTableProvider(NetworkTableProvider provider) {
|
||||
checkInit();
|
||||
staticProvider = provider;
|
||||
}
|
||||
/**
|
||||
* set that network tables should be a server
|
||||
* This must be called before initalize or getTable
|
||||
*/
|
||||
public synchronized static void setServerMode(){
|
||||
checkInit();
|
||||
mode = NetworkTableMode.Server;
|
||||
}
|
||||
/**
|
||||
* set that network tables should be a client
|
||||
* This must be called before initalize or getTable
|
||||
*/
|
||||
public synchronized static void setClientMode(){
|
||||
checkInit();
|
||||
mode = NetworkTableMode.Client;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the team the robot is configured for (this will set the ip address that network tables will connect to in client mode)
|
||||
* This must be called before initalize or getTable
|
||||
* @param team the team number
|
||||
*/
|
||||
public synchronized static void setTeam(int team){
|
||||
setIPAddress("10." + (team / 100) + "." + (team % 100) + ".2");
|
||||
}
|
||||
/**
|
||||
* @param address the adress that network tables will connect to in client mode
|
||||
*/
|
||||
public synchronized static void setIPAddress(final String address){
|
||||
checkInit();
|
||||
ipAddress = address;
|
||||
}
|
||||
/**
|
||||
* Gets the table with the specified key. If the table does not exist, a new table will be created.<br>
|
||||
* This will automatically initialize network tables if it has not been already
|
||||
*
|
||||
* @param key
|
||||
* the key name
|
||||
* @return the network table requested
|
||||
*/
|
||||
public synchronized static NetworkTable getTable(String key) {
|
||||
if(staticProvider==null)
|
||||
try {
|
||||
initialize();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("NetworkTable could not be initialized: "+e+": "+e.getMessage());
|
||||
}
|
||||
return (NetworkTable)staticProvider.getTable(PATH_SEPARATOR+key);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private final String path;
|
||||
private final EntryCache entryCache;
|
||||
private final NetworkTableKeyCache absoluteKeyCache;
|
||||
private final NetworkTableProvider provider;
|
||||
private final NetworkTableNode node;
|
||||
|
||||
NetworkTable(String path, NetworkTableProvider provider) {
|
||||
this.path = path;
|
||||
entryCache = new EntryCache(path);
|
||||
absoluteKeyCache = new NetworkTableKeyCache(path);
|
||||
this.provider = provider;
|
||||
node = provider.getNode();
|
||||
}
|
||||
public String toString(){
|
||||
return "NetworkTable: "+path;
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return node.isConnected();
|
||||
}
|
||||
|
||||
public boolean isServer() {
|
||||
return node.isServer();
|
||||
}
|
||||
|
||||
|
||||
static class NetworkTableKeyCache extends StringCache{
|
||||
private final String path;
|
||||
|
||||
public NetworkTableKeyCache(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public String calc(String key) {
|
||||
return path + PATH_SEPARATOR + key;
|
||||
}
|
||||
}
|
||||
class EntryCache {
|
||||
private final Hashtable cache = new Hashtable();
|
||||
private final String path;
|
||||
|
||||
public EntryCache(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public NetworkTableEntry get(final String key){
|
||||
NetworkTableEntry cachedValue = (NetworkTableEntry)cache.get(key);
|
||||
if(cachedValue==null){
|
||||
cachedValue = node.getEntryStore().getEntry(absoluteKeyCache.get(key));
|
||||
if(cachedValue!=null)
|
||||
cache.put(key, cachedValue);
|
||||
}
|
||||
return cachedValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final Hashtable connectionListenerMap = new Hashtable();
|
||||
public void addConnectionListener(IRemoteConnectionListener listener, boolean immediateNotify) {
|
||||
NetworkTableConnectionListenerAdapter adapter = (NetworkTableConnectionListenerAdapter)connectionListenerMap.get(listener);
|
||||
if(adapter!=null)
|
||||
throw new IllegalStateException("Cannot add the same listener twice");
|
||||
adapter = new NetworkTableConnectionListenerAdapter(this, listener);
|
||||
connectionListenerMap.put(listener, adapter);
|
||||
node.addConnectionListener(adapter, immediateNotify);
|
||||
}
|
||||
|
||||
public void removeConnectionListener(IRemoteConnectionListener listener) {
|
||||
NetworkTableConnectionListenerAdapter adapter = (NetworkTableConnectionListenerAdapter)connectionListenerMap.get(listener);
|
||||
if(adapter!=null)
|
||||
node.removeConnectionListener(adapter);
|
||||
}
|
||||
|
||||
|
||||
public void addTableListener(ITableListener listener) {
|
||||
addTableListener(listener, false);
|
||||
}
|
||||
|
||||
private final Hashtable listenerMap = new Hashtable();
|
||||
public void addTableListener(ITableListener listener, boolean immediateNotify) {
|
||||
List adapters = (List)listenerMap.get(listener);
|
||||
if(adapters==null){
|
||||
adapters = new List();
|
||||
listenerMap.put(listener, adapters);
|
||||
}
|
||||
NetworkTableListenerAdapter adapter = new NetworkTableListenerAdapter(path+PATH_SEPARATOR, this, listener);
|
||||
adapters.add(adapter);
|
||||
node.addTableListener(adapter, immediateNotify);
|
||||
}
|
||||
public void addTableListener(String key, ITableListener listener, boolean immediateNotify) {
|
||||
List adapters = (List)listenerMap.get(listener);
|
||||
if(adapters==null){
|
||||
adapters = new List();
|
||||
listenerMap.put(listener, adapters);
|
||||
}
|
||||
NetworkTableKeyListenerAdapter adapter = new NetworkTableKeyListenerAdapter(key, absoluteKeyCache.get(key), this, listener);
|
||||
adapters.add(adapter);
|
||||
node.addTableListener(adapter, immediateNotify);
|
||||
}
|
||||
public void addSubTableListener(final ITableListener listener) {
|
||||
List adapters = (List)listenerMap.get(listener);
|
||||
if(adapters==null){
|
||||
adapters = new List();
|
||||
listenerMap.put(listener, adapters);
|
||||
}
|
||||
NetworkTableSubListenerAdapter adapter = new NetworkTableSubListenerAdapter(path, this, listener);
|
||||
adapters.add(adapter);
|
||||
node.addTableListener(adapter, true);
|
||||
}
|
||||
|
||||
public void removeTableListener(ITableListener listener) {
|
||||
List adapters = (List)listenerMap.get(listener);
|
||||
if(adapters!=null){
|
||||
for(int i = 0; i<adapters.size(); ++i)
|
||||
node.removeTableListener((ITableListener) adapters.get(i));
|
||||
adapters.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized NetworkTableEntry getEntry(String key){
|
||||
return entryCache.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the table at the specified key. If there is no table at the
|
||||
* specified key, it will create a new table
|
||||
*
|
||||
* @param key
|
||||
* the key name
|
||||
* @return the networktable to be returned
|
||||
*/
|
||||
public synchronized ITable getSubTable(String key) {
|
||||
return (NetworkTable)provider.getTable(absoluteKeyCache.get(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the table and tells if it contains the specified key
|
||||
*
|
||||
* @param key
|
||||
* the key to be checked
|
||||
*/
|
||||
public boolean containsKey(String key) {
|
||||
return node.containsKey(absoluteKeyCache.get(key));
|
||||
}
|
||||
|
||||
public boolean containsSubTable(String key){
|
||||
String subtablePrefix = absoluteKeyCache.get(key)+PATH_SEPARATOR;
|
||||
List keys = node.getEntryStore().keys();
|
||||
for(int i = 0; i<keys.size(); ++i){
|
||||
if(((String)keys.get(i)).startsWith(subtablePrefix))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the specified key to the specified value in this table. The key can
|
||||
* not be null. The value can be retrieved by calling the get method with a
|
||||
* key that is equal to the original key.
|
||||
*
|
||||
* @param key
|
||||
* the key
|
||||
* @param value
|
||||
* the value
|
||||
*/
|
||||
public void putNumber(String key, double value) {
|
||||
putValue(key, new Double(value));//TODO cache doubles
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key that the name maps to.
|
||||
*
|
||||
* @param key
|
||||
* the key name
|
||||
* @return the key
|
||||
* @throws TableKeyNotDefinedException
|
||||
* if the specified key is null
|
||||
*/
|
||||
public double getNumber(String key) throws TableKeyNotDefinedException {
|
||||
return node.getDouble(absoluteKeyCache.get(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key that the name maps to. If the key is null, it will return
|
||||
* the default value
|
||||
*
|
||||
* @param key
|
||||
* the key name
|
||||
* @param defaultValue
|
||||
* the default value if the key is null
|
||||
* @return the key
|
||||
*/
|
||||
public double getNumber(String key, double defaultValue) {
|
||||
try {
|
||||
return node.getDouble(absoluteKeyCache.get(key));
|
||||
} catch (TableKeyNotDefinedException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the specified key to the specified value in this table. The key can
|
||||
* not be null. The value can be retrieved by calling the get method with a
|
||||
* key that is equal to the original key.
|
||||
*
|
||||
* @param key
|
||||
* the key
|
||||
* @param value
|
||||
* the value
|
||||
*/
|
||||
public void putString(String key, String value) {
|
||||
putValue(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key that the name maps to.
|
||||
*
|
||||
* @param key
|
||||
* the key name
|
||||
* @return the key
|
||||
* @throws TableKeyNotDefinedException
|
||||
* if the specified key is null
|
||||
*/
|
||||
public String getString(String key) throws TableKeyNotDefinedException {
|
||||
return node.getString(absoluteKeyCache.get(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key that the name maps to. If the key is null, it will return
|
||||
* the default value
|
||||
*
|
||||
* @param key
|
||||
* the key name
|
||||
* @param defaultValue
|
||||
* the default value if the key is null
|
||||
* @return the key
|
||||
*/
|
||||
public String getString(String key, String defaultValue) {
|
||||
try {
|
||||
return node.getString(absoluteKeyCache.get(key));
|
||||
} catch (TableKeyNotDefinedException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the specified key to the specified value in this table. The key can
|
||||
* not be null. The value can be retrieved by calling the get method with a
|
||||
* key that is equal to the original key.
|
||||
*
|
||||
* @param key
|
||||
* the key
|
||||
* @param value
|
||||
* the value
|
||||
*/
|
||||
public void putBoolean(String key, boolean value) {
|
||||
putValue(key, value?Boolean.TRUE:Boolean.FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key that the name maps to.
|
||||
*
|
||||
* @param key
|
||||
* the key name
|
||||
* @return the key
|
||||
* @throws TableKeyNotDefinedException
|
||||
* if the specified key is null
|
||||
*/
|
||||
public boolean getBoolean(String key) throws TableKeyNotDefinedException {
|
||||
return node.getBoolean(absoluteKeyCache.get(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key that the name maps to. If the key is null, it will return
|
||||
* the default value
|
||||
*
|
||||
* @param key
|
||||
* the key name
|
||||
* @param defaultValue
|
||||
* the default value if the key is null
|
||||
* @return the key
|
||||
*/
|
||||
public boolean getBoolean(String key, boolean defaultValue) {
|
||||
try {
|
||||
return node.getBoolean(absoluteKeyCache.get(key));
|
||||
} catch (TableKeyNotDefinedException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void retrieveValue(String key, Object externalValue) {
|
||||
node.retrieveValue(absoluteKeyCache.get(key), externalValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the specified key to the specified value in this table. The key can
|
||||
* not be null. The value can be retrieved by calling the get method with a
|
||||
* key that is equal to the original key.
|
||||
*
|
||||
* @param key the key name
|
||||
* @param value the value to be put
|
||||
*/
|
||||
public void putValue(String key, Object value){
|
||||
NetworkTableEntry entry = entryCache.get(key);
|
||||
if(entry!=null)
|
||||
node.putValue(entry, value);
|
||||
else
|
||||
node.putValue(absoluteKeyCache.get(key), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key that the name maps to.
|
||||
* NOTE: If the value is a double, it will return a Double object,
|
||||
* not a primitive. To get the primitive, use getDouble
|
||||
*
|
||||
* @param key
|
||||
* the key name
|
||||
* @return the key
|
||||
* @throws TableKeyNotDefinedException
|
||||
* if the specified key is null
|
||||
*/
|
||||
public Object getValue(String key) throws TableKeyNotDefinedException {
|
||||
return node.getValue(absoluteKeyCache.get(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key that the name maps to. If the key is null, it will return
|
||||
* the default value
|
||||
* NOTE: If the value is a double, it will return a Double object,
|
||||
* not a primitive. To get the primitive, use getDouble
|
||||
*
|
||||
* @param key
|
||||
* the key name
|
||||
* @param defaultValue
|
||||
* the default value if the key is null
|
||||
* @return the key
|
||||
*/
|
||||
public Object getValue(String key, Object defaultValue) {
|
||||
try {
|
||||
return node.getValue(absoluteKeyCache.get(key));
|
||||
} catch(TableKeyNotDefinedException e){
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Depricated Methods
|
||||
*/
|
||||
/**
|
||||
* @deprecated
|
||||
* Maps the specified key to the specified value in this table.
|
||||
* The key can not be null.
|
||||
* The value can be retrieved by calling the get method with a key that is equal to the original key.
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @throws IllegalArgumentException if key is null
|
||||
*/
|
||||
public void putInt(String key, int value) {
|
||||
putNumber(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Returns the value at the specified key.
|
||||
* @param key the key
|
||||
* @return the value
|
||||
* @throws TableKeyNotDefinedException if there is no value mapped to by the key
|
||||
* @throws IllegalArgumentException if the value mapped to by the key is not an int
|
||||
* @throws IllegalArgumentException if the key is null
|
||||
*/
|
||||
public int getInt(String key) throws TableKeyNotDefinedException{
|
||||
return (int) getNumber(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Returns the value at the specified key.
|
||||
* @param key the key
|
||||
* @param defaultValue the value returned if the key is undefined
|
||||
* @return the value
|
||||
* @throws NetworkTableKeyNotDefined if there is no value mapped to by the key
|
||||
* @throws IllegalArgumentException if the value mapped to by the key is not an int
|
||||
* @throws IllegalArgumentException if the key is null
|
||||
*/
|
||||
public int getInt(String key, int defaultValue) throws TableKeyNotDefinedException{
|
||||
try {
|
||||
return (int) getNumber(key);
|
||||
} catch (NoSuchElementException ex) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Maps the specified key to the specified value in this table.
|
||||
* The key can not be null.
|
||||
* The value can be retrieved by calling the get method with a key that is equal to the original key.
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @throws IllegalArgumentException if key is null
|
||||
*/
|
||||
public void putDouble(String key, double value) {
|
||||
putNumber(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Returns the value at the specified key.
|
||||
* @param key the key
|
||||
* @return the value
|
||||
* @throws NoSuchEleNetworkTableKeyNotDefinedmentException if there is no value mapped to by the key
|
||||
* @throws IllegalArgumentException if the value mapped to by the key is not a double
|
||||
* @throws IllegalArgumentException if the key is null
|
||||
*/
|
||||
public double getDouble(String key) throws TableKeyNotDefinedException{
|
||||
return getNumber(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Returns the value at the specified key.
|
||||
* @param key the key
|
||||
* @param defaultValue the value returned if the key is undefined
|
||||
* @return the value
|
||||
* @throws NoSuchEleNetworkTableKeyNotDefinedmentException if there is no value mapped to by the key
|
||||
* @throws IllegalArgumentException if the value mapped to by the key is not a double
|
||||
* @throws IllegalArgumentException if the key is null
|
||||
*/
|
||||
public double getDouble(String key, double defaultValue) {
|
||||
return getNumber(key, defaultValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package edu.wpi.first.wpilibj.networktables;
|
||||
|
||||
import edu.wpi.first.wpilibj.tables.*;
|
||||
|
||||
/**
|
||||
* An adapter that changes the source of a connection event
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class NetworkTableConnectionListenerAdapter implements IRemoteConnectionListener {
|
||||
|
||||
private final IRemoteConnectionListener targetListener;
|
||||
private final IRemote targetSource;
|
||||
|
||||
/**
|
||||
* @param targetSource the source where the event will appear to come from
|
||||
* @param targetListener the listener where events will be forwarded
|
||||
*/
|
||||
public NetworkTableConnectionListenerAdapter(IRemote targetSource, IRemoteConnectionListener targetListener){
|
||||
this.targetSource = targetSource;
|
||||
this.targetListener = targetListener;
|
||||
}
|
||||
|
||||
public void connected(IRemote remote) {
|
||||
targetListener.connected(targetSource);
|
||||
}
|
||||
|
||||
public void disconnected(IRemote remote) {
|
||||
targetListener.disconnected(targetSource);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package edu.wpi.first.wpilibj.networktables;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.*;
|
||||
import edu.wpi.first.wpilibj.tables.*;
|
||||
|
||||
/**
|
||||
* An adapter that is used to filter value change notifications for a specific key
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class NetworkTableKeyListenerAdapter implements ITableListener {
|
||||
|
||||
private final ITableListener targetListener;
|
||||
private final NetworkTable targetSource;
|
||||
private final String relativeKey;
|
||||
private final String fullKey;
|
||||
|
||||
/**
|
||||
* Create a new adapter
|
||||
* @param relativeKey the name of the key relative to the table (this is what the listener will receiver as the key)
|
||||
* @param fullKey the full name of the key in the {@link NetworkTableNode}
|
||||
* @param targetSource the source that events passed to the target listener will appear to come from
|
||||
* @param targetListener the listener where events are forwarded to
|
||||
*/
|
||||
public NetworkTableKeyListenerAdapter(String relativeKey, String fullKey, NetworkTable targetSource, ITableListener targetListener){
|
||||
this.relativeKey = relativeKey;
|
||||
this.fullKey = fullKey;
|
||||
this.targetSource = targetSource;
|
||||
this.targetListener = targetListener;
|
||||
}
|
||||
|
||||
public void valueChanged(ITable source, String key, Object value, boolean isNew) {
|
||||
if(key.equals(fullKey)){
|
||||
targetListener.valueChanged(targetSource, relativeKey, value, isNew);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package edu.wpi.first.wpilibj.networktables;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* An exception throw when the lookup a a key-value fails in a {@link NetworkTable}
|
||||
*
|
||||
* @deprecated to provide backwards compatability for new api
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class NetworkTableKeyNotDefined extends NoSuchElementException {
|
||||
|
||||
/**
|
||||
* @param key the key that was not defined in the table
|
||||
*/
|
||||
public NetworkTableKeyNotDefined(String key) {
|
||||
super("Unkown Table Key: "+key);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package edu.wpi.first.wpilibj.networktables;
|
||||
|
||||
import edu.wpi.first.wpilibj.tables.*;
|
||||
|
||||
/**
|
||||
* An adapter that is used to filter value change notifications and make the path relative to the NetworkTable
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class NetworkTableListenerAdapter implements ITableListener {
|
||||
|
||||
private final ITableListener targetListener;
|
||||
private final ITable targetSource;
|
||||
private final String prefix;
|
||||
|
||||
/**
|
||||
* Create a new adapter
|
||||
* @param prefix the prefix that will be filtered/removed from the beginning of the key
|
||||
* @param targetSource the source that events passed to the target listener will appear to come from
|
||||
* @param targetListener the listener where events are forwarded to
|
||||
*/
|
||||
public NetworkTableListenerAdapter(String prefix, ITable targetSource, ITableListener targetListener){
|
||||
this.prefix = prefix;
|
||||
this.targetSource = targetSource;
|
||||
this.targetListener = targetListener;
|
||||
}
|
||||
|
||||
public void valueChanged(ITable source, String key, Object value, boolean isNew) {//TODO use string cache
|
||||
if(key.startsWith(prefix)){
|
||||
String relativeKey = key.substring(prefix.length());
|
||||
if(contains(relativeKey, NetworkTable.PATH_SEPARATOR))
|
||||
return;
|
||||
targetListener.valueChanged(targetSource, relativeKey, value, isNew);
|
||||
}
|
||||
}
|
||||
private static boolean contains(String source, char target){
|
||||
for(int i = 0; i<source.length(); ++i)
|
||||
if(source.charAt(i)==target)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package edu.wpi.first.wpilibj.networktables;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.client.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.server.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.stream.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.thread.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.type.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* Represents a different modes that network tables can be configured in
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public abstract class NetworkTableMode {
|
||||
|
||||
/**
|
||||
* A mode where Network tables will be a server on the specified port
|
||||
*/
|
||||
public static final NetworkTableMode Server = new NetworkTableMode("Server"){
|
||||
public NetworkTableNode createNode(String ipAddress, int port, NTThreadManager threadManager) throws IOException {
|
||||
IOStreamProvider streamProvider = SocketStreams.newStreamProvider(port);
|
||||
return new NetworkTableServer(streamProvider, new NetworkTableEntryTypeManager(), threadManager);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* A mode where network tables will be a client which connects to the specified host and port
|
||||
*/
|
||||
public static final NetworkTableMode Client = new NetworkTableMode("Client"){
|
||||
public NetworkTableNode createNode(String ipAddress, int port, NTThreadManager threadManager) throws IOException {
|
||||
if(ipAddress==null)
|
||||
throw new IllegalArgumentException("IP address cannot be null when in client mode");
|
||||
IOStreamFactory streamFactory = SocketStreams.newStreamFactory(ipAddress, port);
|
||||
NetworkTableClient client = new NetworkTableClient(streamFactory, new NetworkTableEntryTypeManager(), threadManager);
|
||||
client.reconnect();
|
||||
return client;
|
||||
}
|
||||
};
|
||||
private String name;
|
||||
private NetworkTableMode(String name){
|
||||
this.name = name;
|
||||
}
|
||||
public String toString(){
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ipAddress the IP address configured by the user
|
||||
* @param port the port configured by the user
|
||||
* @param threadManager the thread manager that should be used for threads in the node
|
||||
* @return a new node that can back a network table
|
||||
* @throws IOException
|
||||
*/
|
||||
abstract NetworkTableNode createNode(String ipAddress, int port, NTThreadManager threadManager) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package edu.wpi.first.wpilibj.networktables;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.*;
|
||||
import edu.wpi.first.wpilibj.tables.*;
|
||||
|
||||
/**
|
||||
* Provides a {@link NetworkTable} for a given {@link NetworkTableNode}
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class NetworkTableProvider implements ITableProvider{
|
||||
private final NetworkTableNode node;
|
||||
private final Hashtable tables = new Hashtable();
|
||||
|
||||
/**
|
||||
* Create a new NetworkTableProvider for a given NetworkTableNode
|
||||
* @param node the node that handles the actual network table
|
||||
*/
|
||||
public NetworkTableProvider(NetworkTableNode node){
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
public ITable getRootTable(){
|
||||
return getTable("");
|
||||
}
|
||||
|
||||
public ITable getTable(String key) {
|
||||
if (tables.containsKey(key)) {
|
||||
return (NetworkTable) tables.get(key);
|
||||
} else {
|
||||
NetworkTable table = new NetworkTable(key, this);
|
||||
tables.put(key, table);
|
||||
return table;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Network Table node that backs the Tables returned by this provider
|
||||
*/
|
||||
public NetworkTableNode getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* close the backing network table node
|
||||
*/
|
||||
public void close() {
|
||||
node.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package edu.wpi.first.wpilibj.networktables;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.util.*;
|
||||
import edu.wpi.first.wpilibj.tables.*;
|
||||
|
||||
/**
|
||||
* An adapter that is used to filter sub table change notifications and make the path relative to the NetworkTable
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class NetworkTableSubListenerAdapter implements ITableListener {
|
||||
|
||||
private final ITableListener targetListener;
|
||||
private final NetworkTable targetSource;
|
||||
private final String prefix;
|
||||
|
||||
private final Set notifiedTables = new Set();
|
||||
|
||||
/**
|
||||
* Create a new adapter
|
||||
* @param prefix the prefix of the current table
|
||||
* @param targetSource the source that events passed to the target listener will appear to come from
|
||||
* @param targetListener the listener where events are forwarded to
|
||||
*/
|
||||
public NetworkTableSubListenerAdapter(String prefix, NetworkTable targetSource, ITableListener targetListener){
|
||||
this.prefix = prefix;
|
||||
this.targetSource = targetSource;
|
||||
this.targetListener = targetListener;
|
||||
}
|
||||
|
||||
public void valueChanged(ITable source, String key, Object value, boolean isNew) {//TODO use string cache
|
||||
if(key.startsWith(prefix)){
|
||||
String relativeKey = key.substring(prefix.length()+1);
|
||||
int endSubTable = -1;//TODO implement sub table listening better
|
||||
for(int i = 0; i<relativeKey.length(); ++i){
|
||||
if(relativeKey.charAt(i)==NetworkTable.PATH_SEPARATOR){//is sub table
|
||||
endSubTable = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(endSubTable!=-1){
|
||||
String subTableKey = relativeKey.substring(0, endSubTable);
|
||||
if(!notifiedTables.contains(subTableKey)){
|
||||
notifiedTables.add(subTableKey);
|
||||
targetListener.valueChanged(targetSource, subTableKey, targetSource.getSubTable(subTableKey), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
package edu.wpi.first.wpilibj.networktables2;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.type.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.util.*;
|
||||
import edu.wpi.first.wpilibj.tables.*;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
|
||||
/**
|
||||
* An entry store that handles storing entries and applying transactions
|
||||
*
|
||||
* @author mwills
|
||||
* @author Fredric
|
||||
*
|
||||
*/
|
||||
|
||||
public abstract class AbstractNetworkTableEntryStore implements IncomingEntryReceiver{
|
||||
protected final CharacterArrayMap idEntries = new CharacterArrayMap();
|
||||
protected final Hashtable namedEntries = new Hashtable();
|
||||
|
||||
protected final TableListenerManager listenerManager;
|
||||
|
||||
protected AbstractNetworkTableEntryStore(TableListenerManager listenerManager){
|
||||
this.listenerManager = listenerManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an entry based on it's id
|
||||
* @param entryId the id f the entry to look for
|
||||
* @return the entry or null if the entry does not exist
|
||||
*/
|
||||
public NetworkTableEntry getEntry(final char entryId){
|
||||
synchronized(this){
|
||||
return (NetworkTableEntry) idEntries.get(entryId);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get an entry based on it's name
|
||||
* @param name the name of the entry to look for
|
||||
* @return the entry or null if the entry does not exist
|
||||
*/
|
||||
public NetworkTableEntry getEntry(String name){
|
||||
synchronized(this){
|
||||
return (NetworkTableEntry) namedEntries.get(name);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get an entry based on it's name
|
||||
* @param name the name of the entry to look for
|
||||
* @return the entry or null if the entry does not exist
|
||||
*/
|
||||
public List keys(){
|
||||
synchronized(this){
|
||||
List entryKeys = new List();
|
||||
Enumeration e = namedEntries.keys();
|
||||
while(e.hasMoreElements())
|
||||
entryKeys.add(e.nextElement());
|
||||
return entryKeys;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all entries
|
||||
* NOTE: This method should not be used with applications which cache entries which would lead to unknown results
|
||||
* This method is for use in testing only
|
||||
*/
|
||||
public void clearEntries() {
|
||||
synchronized (this) {
|
||||
idEntries.clear();
|
||||
namedEntries.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* clear the id's of all entries
|
||||
*/
|
||||
public void clearIds() {
|
||||
synchronized(this){
|
||||
idEntries.clear();
|
||||
Enumeration e = namedEntries.elements();
|
||||
while(e.hasMoreElements())
|
||||
((NetworkTableEntry)e.nextElement()).clearId();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected OutgoingEntryReceiver outgoingReceiver;
|
||||
protected OutgoingEntryReceiver incomingReceiver;
|
||||
public void setOutgoingReceiver(final OutgoingEntryReceiver receiver){
|
||||
outgoingReceiver = receiver;
|
||||
}
|
||||
public void setIncomingReceiver(OutgoingEntryReceiver receiver){
|
||||
incomingReceiver = receiver;
|
||||
}
|
||||
|
||||
protected abstract boolean addEntry(NetworkTableEntry entry);
|
||||
protected abstract boolean updateEntry(NetworkTableEntry entry, char sequenceNumber, Object value);
|
||||
|
||||
/**
|
||||
* Check if two objects are equal doing a deep equals of arrays
|
||||
* This method assumes that o1 and o2 are of the same type (if one is an object array the other one is also)
|
||||
* @param o1
|
||||
* @param o2
|
||||
*/
|
||||
private static boolean valuesEqual(Object o1, Object o2){
|
||||
if(o1 instanceof Object[]){
|
||||
Object[] a1 = (Object[])o1;
|
||||
Object[] a2 = (Object[])o2;
|
||||
if(a1.length!=a2.length)
|
||||
return false;
|
||||
for(int i = 0; i<a1.length; ++i)
|
||||
if(!valuesEqual(a1[i], a2[i]))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
return o1!=null?o1.equals(o2):o2==null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the given value under the given name and queues it for
|
||||
* transmission to the server.
|
||||
*
|
||||
* @param name The name under which to store the given value.
|
||||
* @param type The type of the given value.
|
||||
* @param value The value to store.
|
||||
* @throws TableKeyExistsWithDifferentTypeException Thrown if an
|
||||
* entry already exists with the given name and is of a different type.
|
||||
*/
|
||||
public void putOutgoing(String name, NetworkTableEntryType type, Object value) throws TableKeyExistsWithDifferentTypeException{
|
||||
synchronized(this){
|
||||
NetworkTableEntry tableEntry = (NetworkTableEntry)namedEntries.get(name);
|
||||
if(tableEntry==null){
|
||||
//TODO validate type
|
||||
tableEntry = new NetworkTableEntry(name, type, value);
|
||||
if(addEntry(tableEntry)){
|
||||
tableEntry.fireListener(listenerManager);
|
||||
outgoingReceiver.offerOutgoingAssignment(tableEntry);
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(tableEntry.getType().id != type.id)
|
||||
throw new edu.wpi.first.wpilibj.networktables2.TableKeyExistsWithDifferentTypeException(name, tableEntry.getType());
|
||||
if(!valuesEqual(value, tableEntry.getValue())){
|
||||
if(updateEntry(tableEntry, (char)(tableEntry.getSequenceNumber()+1), value)){
|
||||
outgoingReceiver.offerOutgoingUpdate(tableEntry);
|
||||
}
|
||||
tableEntry.fireListener(listenerManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void putOutgoing(NetworkTableEntry tableEntry, Object value){
|
||||
synchronized(this){
|
||||
//TODO Validate type
|
||||
if(!valuesEqual(value, tableEntry.getValue())){
|
||||
if(updateEntry(tableEntry, (char)(tableEntry.getSequenceNumber()+1), value)){
|
||||
outgoingReceiver.offerOutgoingUpdate(tableEntry);
|
||||
}
|
||||
tableEntry.fireListener(listenerManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void offerIncomingAssignment(NetworkTableEntry entry) {
|
||||
synchronized(this){
|
||||
NetworkTableEntry tableEntry = (NetworkTableEntry)namedEntries.get(entry.name);
|
||||
if(addEntry(entry)){
|
||||
if(tableEntry==null)
|
||||
tableEntry = entry;
|
||||
tableEntry.fireListener(listenerManager);
|
||||
incomingReceiver.offerOutgoingAssignment(tableEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void offerIncomingUpdate(NetworkTableEntry entry, char sequenceNumber, Object value) {
|
||||
synchronized(this){
|
||||
if(updateEntry(entry, sequenceNumber, value)){
|
||||
entry.fireListener(listenerManager);
|
||||
incomingReceiver.offerOutgoingUpdate(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called to say that a listener should notify the listener manager of all of the entries
|
||||
* @param listener
|
||||
* @param table
|
||||
*/
|
||||
public void notifyEntries(final ITable table, final ITableListener listener) {
|
||||
synchronized(this){
|
||||
Enumeration entryIterator = namedEntries.elements();
|
||||
while(entryIterator.hasMoreElements()){
|
||||
NetworkTableEntry entry = (NetworkTableEntry) entryIterator.nextElement();
|
||||
listener.valueChanged(table, entry.name, entry.getValue(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An object that handles firing Table Listeners
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public interface TableListenerManager {
|
||||
/**
|
||||
* Called when the object should fire it's listeners
|
||||
* @param key
|
||||
* @param value
|
||||
* @param isNew
|
||||
*/
|
||||
void fireTableListeners(String key, Object value, boolean isNew);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package edu.wpi.first.wpilibj.networktables2;
|
||||
|
||||
|
||||
|
||||
public interface FlushableOutgoingEntryReceiver extends OutgoingEntryReceiver{
|
||||
public void flush();
|
||||
|
||||
public void ensureAlive();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package edu.wpi.first.wpilibj.networktables2;
|
||||
|
||||
|
||||
|
||||
public interface IncomingEntryReceiver {
|
||||
IncomingEntryReceiver NULL = new IncomingEntryReceiver() {
|
||||
public void offerIncomingUpdate(NetworkTableEntry entry, char entrySequenceNumber, Object value) {
|
||||
}
|
||||
public void offerIncomingAssignment(NetworkTableEntry entry) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public void offerIncomingAssignment(NetworkTableEntry entry);
|
||||
public void offerIncomingUpdate(NetworkTableEntry entry, char entrySequenceNumber, Object value);
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package edu.wpi.first.wpilibj.networktables2;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.AbstractNetworkTableEntryStore.TableListenerManager;
|
||||
import edu.wpi.first.wpilibj.networktables2.connection.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.type.*;
|
||||
import java.io.*;
|
||||
|
||||
|
||||
/**
|
||||
* An entry in a network table
|
||||
*
|
||||
* @author mwills
|
||||
*
|
||||
*/
|
||||
public class NetworkTableEntry {
|
||||
/**
|
||||
* the id that represents that an id is unknown for an entry
|
||||
*/
|
||||
public static final char UNKNOWN_ID = (char)0xFFFF;
|
||||
|
||||
private char id;
|
||||
private char sequenceNumber;
|
||||
/**
|
||||
* the name of the entry
|
||||
*/
|
||||
public final String name;
|
||||
/**
|
||||
* the type of the entry
|
||||
*/
|
||||
private NetworkTableEntryType type;
|
||||
private Object value;
|
||||
private volatile boolean isNew = true;
|
||||
private volatile boolean isDirty = false;
|
||||
|
||||
/**
|
||||
* Create a new entry with the given name, type, value, an unknown id and a sequence number of 0
|
||||
* @param name
|
||||
* @param type
|
||||
* @param value
|
||||
*/
|
||||
public NetworkTableEntry(final String name, final NetworkTableEntryType type, final Object value){
|
||||
this(UNKNOWN_ID, name, (char)0, type, value);
|
||||
}
|
||||
/**
|
||||
* Create a new entry with the given id, name, sequence number, type and value
|
||||
* @param id
|
||||
* @param name
|
||||
* @param sequenceNumber
|
||||
* @param type
|
||||
* @param value
|
||||
*/
|
||||
public NetworkTableEntry(final char id, final String name, final char sequenceNumber, final NetworkTableEntryType type, final Object value){
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.sequenceNumber = sequenceNumber;
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the id of the entry
|
||||
*/
|
||||
public char getId() {
|
||||
return id;
|
||||
}
|
||||
/**
|
||||
* @return the current value of the entry
|
||||
*/
|
||||
public Object getValue(){
|
||||
return value;
|
||||
}
|
||||
/**
|
||||
* @return the type of the entry
|
||||
*/
|
||||
public NetworkTableEntryType getType(){
|
||||
return type;
|
||||
}
|
||||
private static final char HALF_OF_CHAR = 32768;
|
||||
/**
|
||||
* set the value of the entry if the given sequence number is greater that the current sequence number
|
||||
* @param newSequenceNumber the sequence number of the incoming entry
|
||||
* @param newValue the new value
|
||||
* @return true if the value was set
|
||||
*/
|
||||
public boolean putValue(final char newSequenceNumber, final Object newValue) {
|
||||
if( (sequenceNumber < newSequenceNumber && newSequenceNumber - sequenceNumber < HALF_OF_CHAR)
|
||||
|| (sequenceNumber > newSequenceNumber && sequenceNumber - newSequenceNumber > HALF_OF_CHAR) ){
|
||||
value = newValue;
|
||||
sequenceNumber = newSequenceNumber;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* force a value and new sequence number upon an entry
|
||||
* @param newSequenceNumber
|
||||
* @param newValue
|
||||
*/
|
||||
public void forcePut(final char newSequenceNumber, final Object newValue) {
|
||||
value = newValue;
|
||||
sequenceNumber = newSequenceNumber;
|
||||
}
|
||||
/**
|
||||
* force a value and new sequence number upon an entry, Will also set the type of the entry
|
||||
* @param newSequenceNumber
|
||||
* @param type
|
||||
* @param newValue
|
||||
*/
|
||||
public void forcePut(final char newSequenceNumber, final NetworkTableEntryType type, final Object newValue) {
|
||||
this.type = type;
|
||||
forcePut(newSequenceNumber, newValue);
|
||||
}
|
||||
|
||||
|
||||
public void makeDirty() {
|
||||
isDirty = true;
|
||||
}
|
||||
public void makeClean() {
|
||||
isDirty = false;
|
||||
}
|
||||
public boolean isDirty(){
|
||||
return isDirty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the value of the entry over the output stream
|
||||
* @param os
|
||||
* @throws IOException
|
||||
*/
|
||||
public void sendValue(final DataOutputStream os) throws IOException{
|
||||
type.sendValue(value, os);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current sequence number of the entry
|
||||
*/
|
||||
public char getSequenceNumber() {
|
||||
return sequenceNumber;
|
||||
}
|
||||
/**
|
||||
* Sets the id of the entry
|
||||
* @param id the id of the entry
|
||||
* @throws IllegalStateException if the entry already has a known id
|
||||
*/
|
||||
public void setId(final char id) throws IllegalStateException{
|
||||
if(this.id!=UNKNOWN_ID)
|
||||
throw new IllegalStateException("Cannot set the Id of a table entry that already has a valid id");
|
||||
this.id = id;
|
||||
}
|
||||
/**
|
||||
* clear the id of the entry to unknown
|
||||
*/
|
||||
public void clearId() {
|
||||
id = UNKNOWN_ID;
|
||||
}
|
||||
|
||||
public void send(NetworkTableConnection connection) throws IOException {
|
||||
connection.sendEntryAssignment(this);
|
||||
}
|
||||
public void fireListener(TableListenerManager listenerManager) {//TODO determine best way to handle complex data
|
||||
listenerManager.fireTableListeners(name, value, isNew);
|
||||
isNew = false;
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
return "Network Table "+type.name+" entry: "+name+": "+(int)getId()+" - "+(int)getSequenceNumber()+" - "+getValue();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package edu.wpi.first.wpilibj.networktables2;
|
||||
|
||||
/**
|
||||
* The definitions of all of the protocol message types
|
||||
*
|
||||
* @author mwills
|
||||
*
|
||||
*/
|
||||
public interface NetworkTableMessageType {
|
||||
/**
|
||||
* A keep alive message that the client sends
|
||||
*/
|
||||
int KEEP_ALIVE = 0x00;
|
||||
/**
|
||||
* a client hello message that a client sends
|
||||
*/
|
||||
int CLIENT_HELLO = 0x01;
|
||||
/**
|
||||
* a protocol version unsupported message that the server sends to a client
|
||||
*/
|
||||
int PROTOCOL_VERSION_UNSUPPORTED = 0x02;
|
||||
int SERVER_HELLO_COMPLETE = 0x03;
|
||||
/**
|
||||
* an entry assignment message
|
||||
*/
|
||||
int ENTRY_ASSIGNMENT = 0x10;
|
||||
/**
|
||||
* a field update message
|
||||
*/
|
||||
int FIELD_UPDATE = 0x11;
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package edu.wpi.first.wpilibj.networktables2;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.AbstractNetworkTableEntryStore.TableListenerManager;
|
||||
import edu.wpi.first.wpilibj.networktables2.client.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.type.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.util.*;
|
||||
import edu.wpi.first.wpilibj.tables.*;
|
||||
|
||||
/**
|
||||
* represents a node (either a client or a server) in a network tables 2.0
|
||||
* <br>
|
||||
* implementers of the class must ensure that they call {@link #init(NetworkTableTransactionPool, AbstractNetworkTableEntryStore)} before calling any other methods on this class
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public abstract class NetworkTableNode implements TableListenerManager, ClientConnectionListenerManager, IRemote{
|
||||
|
||||
protected AbstractNetworkTableEntryStore entryStore;
|
||||
|
||||
|
||||
protected final void init(AbstractNetworkTableEntryStore entryStore) {
|
||||
this.entryStore = entryStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the entry store used by this node
|
||||
*/
|
||||
public AbstractNetworkTableEntryStore getEntryStore(){
|
||||
return entryStore;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void putBoolean(String name, boolean value){
|
||||
putValue(name, DefaultEntryTypes.BOOLEAN, value?Boolean.TRUE:Boolean.FALSE);
|
||||
}
|
||||
public boolean getBoolean(String name) throws TableKeyNotDefinedException{
|
||||
NetworkTableEntry entry = entryStore.getEntry(name);
|
||||
if(entry==null)
|
||||
throw new TableKeyNotDefinedException(name);
|
||||
return ((Boolean)entry.getValue()).booleanValue();
|
||||
}
|
||||
|
||||
public void putDouble(String name, double value){
|
||||
putValue(name, DefaultEntryTypes.DOUBLE, new Double(value));//TODO don't make a new double every time
|
||||
}
|
||||
public double getDouble(String name) throws TableKeyNotDefinedException{
|
||||
NetworkTableEntry entry = entryStore.getEntry(name);
|
||||
if(entry==null)
|
||||
throw new TableKeyNotDefinedException(name);
|
||||
return ((Double)entry.getValue()).doubleValue();
|
||||
}
|
||||
|
||||
public void putString(String name, String value){
|
||||
putValue(name, DefaultEntryTypes.STRING, value);
|
||||
}
|
||||
public String getString(String name) throws TableKeyNotDefinedException{
|
||||
NetworkTableEntry entry = entryStore.getEntry(name);
|
||||
if(entry==null)
|
||||
throw new TableKeyNotDefinedException(name);
|
||||
return ((String)entry.getValue());
|
||||
}
|
||||
|
||||
public void putComplex(String name, ComplexData value){
|
||||
putValue(name, value.getType(), value);
|
||||
}
|
||||
|
||||
public void retrieveValue(String name, Object externalData) throws TableKeyNotDefinedException{
|
||||
synchronized(entryStore){
|
||||
NetworkTableEntry entry = entryStore.getEntry(name);
|
||||
if(entry==null)
|
||||
throw new TableKeyNotDefinedException(name);
|
||||
NetworkTableEntryType entryType = entry.getType();
|
||||
if(!(entryType instanceof ComplexEntryType))
|
||||
throw new TableKeyExistsWithDifferentTypeException(name, entryType, "Is not a complex data type");
|
||||
ComplexEntryType complexType = (ComplexEntryType)entryType;
|
||||
complexType.exportValue(name, entry.getValue(), externalData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void putValue(String name, Object value) throws IllegalArgumentException{
|
||||
if(value instanceof Double){
|
||||
putValue(name, DefaultEntryTypes.DOUBLE, value);
|
||||
} else if (value instanceof String){
|
||||
putValue(name, DefaultEntryTypes.STRING, value);
|
||||
} else if(value instanceof Boolean){
|
||||
putValue(name, DefaultEntryTypes.BOOLEAN, value);
|
||||
} else if(value instanceof ComplexData){
|
||||
putValue(name, ((ComplexData)value).getType(), value);
|
||||
} else if(value==null) {
|
||||
throw new NullPointerException("Cannot put a null value into networktables");
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid Type");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a value with a specific network table type
|
||||
* @param name the name of the entry to associate with the given value
|
||||
* @param type the type of the entry
|
||||
* @param value the actual value of the entry
|
||||
*/
|
||||
public void putValue(String name, NetworkTableEntryType type, Object value){
|
||||
if(type instanceof ComplexEntryType){
|
||||
synchronized(entryStore){//must sync because use get
|
||||
ComplexEntryType entryType = (ComplexEntryType)type;
|
||||
NetworkTableEntry entry = entryStore.getEntry(name);
|
||||
if(entry!=null)
|
||||
entryStore.putOutgoing(entry, entryType.internalizeValue(entry.name, value, entry.getValue()));
|
||||
else
|
||||
entryStore.putOutgoing(name, type, entryType.internalizeValue(name, value, null));
|
||||
}
|
||||
}
|
||||
else
|
||||
entryStore.putOutgoing(name, type, value);
|
||||
}
|
||||
|
||||
public void putValue(NetworkTableEntry entry, Object value){
|
||||
if(entry.getType() instanceof ComplexEntryType){
|
||||
synchronized(entryStore){//must sync because use get
|
||||
ComplexEntryType entryType = (ComplexEntryType)entry.getType();
|
||||
entryStore.putOutgoing(entry, entryType.internalizeValue(entry.name, value, entry.getValue()));
|
||||
}
|
||||
}
|
||||
else
|
||||
entryStore.putOutgoing(entry, value);
|
||||
}
|
||||
|
||||
public Object getValue(String name) throws TableKeyNotDefinedException{//TODO don't allow get of complex types
|
||||
synchronized(entryStore){
|
||||
NetworkTableEntry entry = entryStore.getEntry(name);
|
||||
if(entry == null)
|
||||
throw new TableKeyNotDefinedException(name);
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param key the key to check for existence
|
||||
* @return true if the table has the given key
|
||||
*/
|
||||
public boolean containsKey(final String key){
|
||||
return entryStore.getEntry(key)!=null;
|
||||
}
|
||||
|
||||
/**
|
||||
* close all networking activity related to this node
|
||||
*/
|
||||
public abstract void close();
|
||||
|
||||
private final List remoteListeners = new List();
|
||||
public void addConnectionListener(IRemoteConnectionListener listener, boolean immediateNotify) {
|
||||
remoteListeners.add(listener);
|
||||
if(isConnected())
|
||||
listener.connected(this);
|
||||
else
|
||||
listener.disconnected(this);
|
||||
}
|
||||
public void removeConnectionListener(IRemoteConnectionListener listener) {
|
||||
remoteListeners.remove(listener);
|
||||
}
|
||||
public void fireConnectedEvent(){
|
||||
for(int i = 0; i<remoteListeners.size(); ++i)
|
||||
((IRemoteConnectionListener)remoteListeners.get(i)).connected(this);
|
||||
}
|
||||
public void fireDisconnectedEvent(){
|
||||
for(int i = 0; i<remoteListeners.size(); ++i)
|
||||
((IRemoteConnectionListener)remoteListeners.get(i)).disconnected(this);
|
||||
}
|
||||
|
||||
|
||||
private final List tableListeners = new List();
|
||||
public void addTableListener(ITableListener listener, boolean immediateNotify) {
|
||||
tableListeners.add(listener);
|
||||
if(immediateNotify)
|
||||
entryStore.notifyEntries(null, listener);
|
||||
}
|
||||
public void removeTableListener(ITableListener listener) {
|
||||
tableListeners.remove(listener);
|
||||
}
|
||||
public void fireTableListeners(String key, Object value, boolean isNew){
|
||||
for(int i = 0; i<tableListeners.size(); ++i)
|
||||
((ITableListener)tableListeners.get(i)).valueChanged(null, key, value, isNew);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package edu.wpi.first.wpilibj.networktables2;
|
||||
|
||||
|
||||
|
||||
public interface OutgoingEntryReceiver {
|
||||
OutgoingEntryReceiver NULL = new OutgoingEntryReceiver() {
|
||||
public void offerOutgoingUpdate(NetworkTableEntry entry) {
|
||||
}
|
||||
public void offerOutgoingAssignment(NetworkTableEntry entry) {
|
||||
}
|
||||
};
|
||||
|
||||
public void offerOutgoingAssignment(NetworkTableEntry entry);
|
||||
public void offerOutgoingUpdate(NetworkTableEntry entry);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package edu.wpi.first.wpilibj.networktables2;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.type.NetworkTableEntryType;
|
||||
|
||||
/**
|
||||
* Throw to indicate that an attempt to put data to a table is illegal because
|
||||
* the specified key exists with a different data type than the put data type.
|
||||
*
|
||||
* @author Paul Malmsten <pmalmsten@gmail.com>
|
||||
*/
|
||||
public class TableKeyExistsWithDifferentTypeException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Creates a new TableKeyExistsWithDifferentTypeException
|
||||
*
|
||||
* @param existingKey The name of the key which exists.
|
||||
* @param existingType The type of the key which exists.
|
||||
*/
|
||||
public TableKeyExistsWithDifferentTypeException(String existingKey, NetworkTableEntryType existingType) {
|
||||
this(existingKey, existingType, "");
|
||||
}
|
||||
|
||||
public TableKeyExistsWithDifferentTypeException(String existingKey, NetworkTableEntryType existingType, String message) {
|
||||
super("Illegal put - key '" + existingKey + "' exists with type '" + existingType + "'. "+message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package edu.wpi.first.wpilibj.networktables2;
|
||||
|
||||
|
||||
/**
|
||||
* A transaction receiver that marks all Table entries as dirty in the entry store. Entries will not be passed to the continuing receiver if they are already dirty
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class TransactionDirtier implements OutgoingEntryReceiver {
|
||||
private final OutgoingEntryReceiver continuingReceiver;
|
||||
|
||||
public TransactionDirtier(final OutgoingEntryReceiver continuingReceiver) {
|
||||
this.continuingReceiver = continuingReceiver;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void offerOutgoingAssignment(NetworkTableEntry entry) {
|
||||
if(entry.isDirty())
|
||||
return;
|
||||
entry.makeDirty();
|
||||
continuingReceiver.offerOutgoingAssignment(entry);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void offerOutgoingUpdate(NetworkTableEntry entry) {
|
||||
if(entry.isDirty())
|
||||
return;
|
||||
entry.makeDirty();
|
||||
continuingReceiver.offerOutgoingUpdate(entry);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
|
||||
package edu.wpi.first.wpilibj.networktables2;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.thread.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.util.*;
|
||||
|
||||
/**
|
||||
* A write manager is a {@link IncomingEntryReceiver} that buffers transactions and then and then dispatches them to a flushable transaction receiver that is periodically offered all queued transaction and then flushed
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class WriteManager implements OutgoingEntryReceiver, PeriodicRunnable{
|
||||
private final int SLEEP_TIME = 100;
|
||||
|
||||
private final int queueSize = 500;
|
||||
|
||||
private Object transactionsLock = new Object();
|
||||
private NTThread thread;
|
||||
private NTThreadManager threadManager;
|
||||
private final AbstractNetworkTableEntryStore entryStore;
|
||||
|
||||
private volatile HalfQueue incomingAssignmentQueue;
|
||||
private volatile HalfQueue incomingUpdateQueue;
|
||||
private volatile HalfQueue outgoingAssignmentQueue;
|
||||
private volatile HalfQueue outgoingUpdateQueue;
|
||||
|
||||
private FlushableOutgoingEntryReceiver receiver;
|
||||
private long lastWrite;
|
||||
|
||||
private final long keepAliveDelay;
|
||||
|
||||
/**
|
||||
* Create a new Write manager
|
||||
* @param receiver
|
||||
* @param threadManager
|
||||
* @param transactionPool
|
||||
* @param entryStore
|
||||
*/
|
||||
public WriteManager(final FlushableOutgoingEntryReceiver receiver, final NTThreadManager threadManager, final AbstractNetworkTableEntryStore entryStore, long keepAliveDelay) {
|
||||
this.receiver = receiver;
|
||||
this.threadManager = threadManager;
|
||||
this.entryStore = entryStore;
|
||||
|
||||
incomingAssignmentQueue = new HalfQueue(queueSize);
|
||||
incomingUpdateQueue = new HalfQueue(queueSize);
|
||||
outgoingAssignmentQueue = new HalfQueue(queueSize);
|
||||
outgoingUpdateQueue = new HalfQueue(queueSize);
|
||||
|
||||
this.keepAliveDelay = keepAliveDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* start the write thread
|
||||
*/
|
||||
public void start(){
|
||||
if(thread!=null)
|
||||
stop();
|
||||
lastWrite = System.currentTimeMillis();
|
||||
thread = threadManager.newBlockingPeriodicThread(this, "Write Manager Thread");
|
||||
}
|
||||
/**
|
||||
* stop the write thread
|
||||
*/
|
||||
public void stop(){
|
||||
if(thread!=null)
|
||||
thread.stop();
|
||||
}
|
||||
|
||||
|
||||
public void offerOutgoingAssignment(NetworkTableEntry entry) {
|
||||
synchronized(transactionsLock){
|
||||
incomingAssignmentQueue.queue(entry);
|
||||
if(incomingAssignmentQueue.isFull()){
|
||||
try {
|
||||
run();
|
||||
} catch (InterruptedException e) {}
|
||||
System.err.println("assignment queue overflowed. decrease the rate at which you create new entries or increase the write buffer size");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void offerOutgoingUpdate(NetworkTableEntry entry) {
|
||||
synchronized(transactionsLock){
|
||||
incomingUpdateQueue.queue(entry);
|
||||
if(incomingUpdateQueue.isFull()){
|
||||
try {
|
||||
run();
|
||||
} catch (InterruptedException e) {}
|
||||
System.err.println("update queue overflowed. decrease the rate at which you update entries or increase the write buffer size");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* the periodic method that sends all buffered transactions
|
||||
*/
|
||||
public void run() throws InterruptedException {
|
||||
synchronized(transactionsLock){
|
||||
//swap the assignment and update queue
|
||||
HalfQueue tmp = incomingAssignmentQueue;
|
||||
incomingAssignmentQueue = outgoingAssignmentQueue;
|
||||
outgoingAssignmentQueue = tmp;
|
||||
|
||||
tmp = incomingUpdateQueue;
|
||||
incomingUpdateQueue = outgoingUpdateQueue;
|
||||
outgoingUpdateQueue = tmp;
|
||||
}
|
||||
|
||||
boolean wrote = false;
|
||||
NetworkTableEntry entry;
|
||||
int i;
|
||||
int size = outgoingAssignmentQueue.size();
|
||||
Object[] array = outgoingAssignmentQueue.array;
|
||||
for(i = 0; i<size; ++i){
|
||||
entry = (NetworkTableEntry)array[i];
|
||||
synchronized(entryStore){
|
||||
entry.makeClean();
|
||||
}
|
||||
wrote = true;
|
||||
receiver.offerOutgoingAssignment(entry);
|
||||
}
|
||||
outgoingAssignmentQueue.clear();
|
||||
|
||||
|
||||
size = outgoingUpdateQueue.size();
|
||||
array = outgoingUpdateQueue.array;
|
||||
for(i = 0; i<size; ++i){
|
||||
entry = (NetworkTableEntry)array[i];
|
||||
synchronized(entryStore){
|
||||
entry.makeClean();
|
||||
}
|
||||
wrote = true;
|
||||
receiver.offerOutgoingUpdate(entry);
|
||||
}
|
||||
outgoingUpdateQueue.clear();
|
||||
|
||||
|
||||
if(wrote){
|
||||
receiver.flush();
|
||||
lastWrite = System.currentTimeMillis();
|
||||
}
|
||||
else if(System.currentTimeMillis()-lastWrite>keepAliveDelay)
|
||||
receiver.ensureAlive();
|
||||
|
||||
Thread.sleep(SLEEP_TIME);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.client;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.connection.BadMessageException;
|
||||
import java.io.*;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.connection.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.stream.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.thread.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.type.*;
|
||||
|
||||
/**
|
||||
* Object that adapts messages from a server
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class ClientConnectionAdapter implements ConnectionAdapter, IncomingEntryReceiver, FlushableOutgoingEntryReceiver{
|
||||
|
||||
private final ClientNetworkTableEntryStore entryStore;
|
||||
private final IOStreamFactory streamFactory;
|
||||
private final NTThreadManager threadManager;
|
||||
|
||||
private NetworkTableConnection connection;
|
||||
private NTThread readThread;
|
||||
private ClientConnectionState connectionState = ClientConnectionState.DISCONNECTED_FROM_SERVER;
|
||||
private final ClientConnectionListenerManager connectionListenerManager;
|
||||
private final Object connectionLock = new Object();
|
||||
private final NetworkTableEntryTypeManager typeManager;
|
||||
|
||||
private void gotoState(ClientConnectionState newState){
|
||||
synchronized(connectionLock){
|
||||
if(connectionState!=newState){
|
||||
System.out.println(this+" entered connection state: "+newState);
|
||||
if(newState==ClientConnectionState.IN_SYNC_WITH_SERVER)
|
||||
connectionListenerManager.fireConnectedEvent();
|
||||
if(connectionState==ClientConnectionState.IN_SYNC_WITH_SERVER)
|
||||
connectionListenerManager.fireDisconnectedEvent();
|
||||
connectionState = newState;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @return the state of the connection
|
||||
*/
|
||||
public ClientConnectionState getConnectionState(){
|
||||
return connectionState;
|
||||
}
|
||||
/**
|
||||
* @return if the client is connected to the server
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return getConnectionState()==ClientConnectionState.IN_SYNC_WITH_SERVER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new ClientConnectionAdapter
|
||||
* @param entryStore
|
||||
* @param threadManager
|
||||
* @param streamFactory
|
||||
* @param transactionPool
|
||||
* @param connectionListenerManager
|
||||
*/
|
||||
public ClientConnectionAdapter(final ClientNetworkTableEntryStore entryStore, final NTThreadManager threadManager, final IOStreamFactory streamFactory, final ClientConnectionListenerManager connectionListenerManager, final NetworkTableEntryTypeManager typeManager) {
|
||||
this.entryStore = entryStore;
|
||||
this.streamFactory = streamFactory;
|
||||
this.threadManager = threadManager;
|
||||
this.connectionListenerManager = connectionListenerManager;
|
||||
this.typeManager = typeManager;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Connection management
|
||||
*/
|
||||
/**
|
||||
* Reconnect the client to the server (even if the client is not currently connected)
|
||||
*/
|
||||
public void reconnect() {
|
||||
synchronized(connectionLock){
|
||||
close();//close the existing stream and monitor thread if needed
|
||||
try{
|
||||
IOStream stream = streamFactory.createStream();
|
||||
if(stream==null)
|
||||
return;
|
||||
connection = new NetworkTableConnection(stream, typeManager);
|
||||
readThread = threadManager.newBlockingPeriodicThread(new ConnectionMonitorThread(this, connection), "Client Connection Reader Thread");
|
||||
connection.sendClientHello();
|
||||
gotoState(ClientConnectionState.CONNECTED_TO_SERVER);
|
||||
} catch(Exception e){
|
||||
close();//make sure to clean everything up if we fail to connect
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the client connection
|
||||
*/
|
||||
public void close() {
|
||||
close(ClientConnectionState.DISCONNECTED_FROM_SERVER);
|
||||
}
|
||||
/**
|
||||
* Close the connection to the server and enter the given state
|
||||
* @param newState
|
||||
*/
|
||||
public void close(final ClientConnectionState newState) {
|
||||
synchronized(connectionLock){
|
||||
gotoState(newState);
|
||||
if(readThread!=null){
|
||||
readThread.stop();
|
||||
readThread = null;
|
||||
}
|
||||
if(connection!=null){
|
||||
connection.close();
|
||||
connection = null;
|
||||
}
|
||||
entryStore.clearIds();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void badMessage(BadMessageException e) {
|
||||
close(new ClientConnectionState.Error(e));
|
||||
}
|
||||
|
||||
public void ioException(IOException e) {
|
||||
if(connectionState!=ClientConnectionState.DISCONNECTED_FROM_SERVER)//will get io exception when on read thread connection is closed
|
||||
reconnect();
|
||||
//gotoState(new ClientConnectionState.Error(e));
|
||||
}
|
||||
|
||||
public NetworkTableEntry getEntry(char id) {
|
||||
return entryStore.getEntry(id);
|
||||
}
|
||||
|
||||
|
||||
public void keepAlive() throws IOException {
|
||||
}
|
||||
|
||||
public void clientHello(char protocolRevision) throws IOException {
|
||||
throw new BadMessageException("A client should not receive a client hello message");
|
||||
}
|
||||
|
||||
public void protocolVersionUnsupported(char protocolRevision) {
|
||||
close();
|
||||
gotoState(new ClientConnectionState.ProtocolUnsuppotedByServer(protocolRevision));
|
||||
}
|
||||
|
||||
public void serverHelloComplete() throws IOException {
|
||||
if (connectionState==ClientConnectionState.CONNECTED_TO_SERVER) {
|
||||
try {
|
||||
gotoState(ClientConnectionState.IN_SYNC_WITH_SERVER);
|
||||
entryStore.sendUnknownEntries(connection);
|
||||
} catch (IOException e) {
|
||||
ioException(e);
|
||||
}
|
||||
}
|
||||
else
|
||||
throw new BadMessageException("A client should only receive a server hello complete once and only after it has connected to the server");
|
||||
}
|
||||
|
||||
|
||||
public void offerIncomingAssignment(NetworkTableEntry entry) {
|
||||
entryStore.offerIncomingAssignment(entry);
|
||||
}
|
||||
public void offerIncomingUpdate(NetworkTableEntry entry, char sequenceNumber, Object value) {
|
||||
entryStore.offerIncomingUpdate(entry, sequenceNumber, value);
|
||||
}
|
||||
|
||||
public void offerOutgoingAssignment(NetworkTableEntry entry) {
|
||||
try {
|
||||
synchronized(connectionLock){
|
||||
if(connection!=null && connectionState==ClientConnectionState.IN_SYNC_WITH_SERVER)
|
||||
connection.sendEntryAssignment(entry);
|
||||
}
|
||||
} catch(IOException e){
|
||||
ioException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void offerOutgoingUpdate(NetworkTableEntry entry) {
|
||||
try {
|
||||
synchronized(connectionLock){
|
||||
if(connection!=null && connectionState==ClientConnectionState.IN_SYNC_WITH_SERVER)
|
||||
connection.sendEntryUpdate(entry);
|
||||
}
|
||||
} catch(IOException e){
|
||||
ioException(e);
|
||||
}
|
||||
}
|
||||
public void flush() {
|
||||
synchronized(connectionLock){
|
||||
if(connection!=null) {
|
||||
try {
|
||||
connection.flush();
|
||||
} catch (IOException e) {
|
||||
ioException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public void ensureAlive() {
|
||||
synchronized(connectionLock){
|
||||
if(connection!=null) {
|
||||
try {
|
||||
connection.sendKeepAlive();
|
||||
} catch (IOException e) {
|
||||
ioException(e);
|
||||
}
|
||||
}
|
||||
else
|
||||
reconnect();//try to reconnect if not connected
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.client;
|
||||
|
||||
/**
|
||||
* An object that manages connection listeners and fires events for other listeners
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public interface ClientConnectionListenerManager {
|
||||
/**
|
||||
* called when something is connected
|
||||
*/
|
||||
void fireConnectedEvent();
|
||||
/**
|
||||
* called when something is disconnected
|
||||
*/
|
||||
void fireDisconnectedEvent();
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.client;
|
||||
|
||||
/**
|
||||
* Represents a state that the client is in
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class ClientConnectionState {
|
||||
/**
|
||||
* indicates that the client is disconnected from the server
|
||||
*/
|
||||
public static final ClientConnectionState DISCONNECTED_FROM_SERVER = new ClientConnectionState("DISCONNECTED_FROM_SERVER");
|
||||
/**
|
||||
* indicates that the client is connected to the server but has not yet begun communication
|
||||
*/
|
||||
public static final ClientConnectionState CONNECTED_TO_SERVER = new ClientConnectionState("CONNECTED_TO_SERVER");
|
||||
/**
|
||||
* represents that the client has sent the hello to the server and is waiting for a response
|
||||
*/
|
||||
public static final ClientConnectionState SENT_HELLO_TO_SERVER = new ClientConnectionState("SENT_HELLO_TO_SERVER");
|
||||
/**
|
||||
* represents that the client is now in sync with the server
|
||||
*/
|
||||
public static final ClientConnectionState IN_SYNC_WITH_SERVER = new ClientConnectionState("IN_SYNC_WITH_SERVER");
|
||||
|
||||
/**
|
||||
* Represents that a client received a message from the server indicating that the client's protocol revision is not supported by the server
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public static class ProtocolUnsuppotedByServer extends ClientConnectionState{
|
||||
private final char serverVersion;
|
||||
/**
|
||||
* Create a new protocol unsupported state
|
||||
* @param serverVersion
|
||||
*/
|
||||
public ProtocolUnsuppotedByServer(final char serverVersion){
|
||||
super("PROTOCOL_UNSUPPORTED_BY_SERVER");
|
||||
this.serverVersion = serverVersion;
|
||||
}
|
||||
/**
|
||||
* @return the protocol version that the server reported it supports
|
||||
*/
|
||||
public char getServerVersion(){
|
||||
return serverVersion;
|
||||
}
|
||||
public String toString(){
|
||||
return "PROTOCOL_UNSUPPORTED_BY_SERVER: Server Version: 0x"+Integer.toHexString(serverVersion);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Represents that the client is in an error state
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public static class Error extends ClientConnectionState{
|
||||
private final Exception e;
|
||||
/**
|
||||
* Create a new error state
|
||||
* @param e
|
||||
*/
|
||||
public Error(final Exception e){
|
||||
super("CLIENT_ERROR");
|
||||
this.e = e;
|
||||
}
|
||||
/**
|
||||
* @return the exception that caused the client to enter an error state
|
||||
*/
|
||||
public Exception getException(){
|
||||
return e;
|
||||
}
|
||||
public String toString(){
|
||||
return "CLIENT_ERROR: "+e.getClass()+": "+e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private String name;
|
||||
protected ClientConnectionState(String name){
|
||||
this.name = name;
|
||||
}
|
||||
public String toString(){
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.client;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.connection.*;
|
||||
|
||||
/**
|
||||
* The entry store for a {@link NetworkTableClient}
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class ClientNetworkTableEntryStore extends AbstractNetworkTableEntryStore{
|
||||
|
||||
/**
|
||||
* Create a new ClientNetworkTableEntryStore
|
||||
* @param transactionPool
|
||||
* @param listenerManager
|
||||
*/
|
||||
public ClientNetworkTableEntryStore(final TableListenerManager listenerManager) {
|
||||
super(listenerManager);
|
||||
}
|
||||
|
||||
|
||||
protected boolean addEntry(NetworkTableEntry newEntry){
|
||||
synchronized(this){
|
||||
NetworkTableEntry entry = (NetworkTableEntry)namedEntries.get(newEntry.name);
|
||||
|
||||
if(entry!=null){
|
||||
if(entry.getId()!=newEntry.getId()){
|
||||
idEntries.remove(entry.getId());
|
||||
if(newEntry.getId()!=NetworkTableEntry.UNKNOWN_ID){
|
||||
entry.setId(newEntry.getId());
|
||||
idEntries.put(newEntry.getId(), entry);
|
||||
}
|
||||
}
|
||||
|
||||
entry.forcePut(newEntry.getSequenceNumber(), newEntry.getType(), newEntry.getValue());
|
||||
}
|
||||
else{
|
||||
if(newEntry.getId()!=NetworkTableEntry.UNKNOWN_ID)
|
||||
idEntries.put(newEntry.getId(), newEntry);
|
||||
namedEntries.put(newEntry.name, newEntry);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean updateEntry(NetworkTableEntry entry, char sequenceNumber, Object value) {
|
||||
synchronized(this){
|
||||
entry.forcePut(sequenceNumber, value);
|
||||
if(entry.getId()==NetworkTableEntry.UNKNOWN_ID){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send all unknown entries in the entry store to the given connection
|
||||
* @param connection
|
||||
* @throws IOException
|
||||
*/
|
||||
void sendUnknownEntries(final NetworkTableConnection connection) throws IOException {
|
||||
synchronized(this){
|
||||
Enumeration e = namedEntries.elements();
|
||||
while(e.hasMoreElements()){
|
||||
NetworkTableEntry entry = (NetworkTableEntry) e.nextElement();
|
||||
if(entry.getId()==NetworkTableEntry.UNKNOWN_ID)
|
||||
connection.sendEntryAssignment(entry);
|
||||
}
|
||||
connection.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.client;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.stream.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.thread.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.type.*;
|
||||
|
||||
/**
|
||||
* A client node in NetworkTables 2.0
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class NetworkTableClient extends NetworkTableNode{
|
||||
private final ClientConnectionAdapter adapter;
|
||||
private final WriteManager writeManager;
|
||||
|
||||
/**
|
||||
* Create a new NetworkTable Client
|
||||
* @param streamFactory
|
||||
* @param threadManager
|
||||
* @param transactionPool
|
||||
*/
|
||||
public NetworkTableClient(final IOStreamFactory streamFactory, final NetworkTableEntryTypeManager typeManager, final NTThreadManager threadManager){
|
||||
ClientNetworkTableEntryStore entryStore;
|
||||
init(entryStore = new ClientNetworkTableEntryStore(this));
|
||||
adapter = new ClientConnectionAdapter(entryStore, threadManager, streamFactory, this, typeManager);
|
||||
writeManager = new WriteManager(adapter, threadManager, getEntryStore(), 1000);
|
||||
|
||||
getEntryStore().setOutgoingReceiver(new TransactionDirtier(writeManager));
|
||||
getEntryStore().setIncomingReceiver(OutgoingEntryReceiver.NULL);
|
||||
writeManager.start();
|
||||
}
|
||||
/**
|
||||
* Create a new NetworkTable Client
|
||||
* @param streamFactory
|
||||
*/
|
||||
public NetworkTableClient(final IOStreamFactory streamFactory){
|
||||
this(streamFactory, new NetworkTableEntryTypeManager(), new DefaultThreadManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* force the client to disconnect and reconnect to the server again. Will connect if the client is currently disconnected
|
||||
*/
|
||||
public void reconnect() {
|
||||
adapter.reconnect();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
adapter.close();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
writeManager.stop();
|
||||
close();
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return adapter.isConnected();
|
||||
}
|
||||
|
||||
public boolean isServer() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.connection;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* An exception throw when a NetworkTableNode receives a bad message
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class BadMessageException extends IOException {
|
||||
/**
|
||||
* Create a new exception
|
||||
* @param message a message
|
||||
*/
|
||||
public BadMessageException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.connection;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.*;
|
||||
|
||||
/**
|
||||
* Handles logic specific to the type of a node
|
||||
*
|
||||
* @author mwills
|
||||
*
|
||||
*/
|
||||
public interface ConnectionAdapter extends IncomingEntryReceiver{
|
||||
/**
|
||||
* Called when the connection receives a keep alive message
|
||||
* @throws IOException
|
||||
*/
|
||||
public void keepAlive() throws IOException;
|
||||
|
||||
/**
|
||||
* Called when the connection receives a client hello message
|
||||
* @param protocolRevision
|
||||
* @throws IOException
|
||||
*/
|
||||
public void clientHello(char protocolRevision) throws IOException;
|
||||
|
||||
/**
|
||||
* Called when the connection receives a protocol unsupported message
|
||||
* @param protocolRevision the protocol version the server reported it supports
|
||||
* @throws IOException
|
||||
*/
|
||||
public void protocolVersionUnsupported(char protocolRevision) throws IOException;
|
||||
|
||||
public void serverHelloComplete() throws IOException;
|
||||
|
||||
|
||||
/**
|
||||
* get an entry (used by a connection when filling an update entry
|
||||
* @param id
|
||||
* @return the entry or null if the entry does not exist
|
||||
*/
|
||||
public NetworkTableEntry getEntry(char id);
|
||||
|
||||
/**
|
||||
* called if a bad message exception is thrown
|
||||
* @param e
|
||||
*/
|
||||
public void badMessage(BadMessageException e);
|
||||
|
||||
/**
|
||||
* called if an io exception is thrown
|
||||
* @param e
|
||||
*/
|
||||
public void ioException(IOException e);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.connection;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.thread.*;
|
||||
|
||||
|
||||
/**
|
||||
* A periodic thread that repeatedly reads from a connection
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class ConnectionMonitorThread implements PeriodicRunnable{
|
||||
private final ConnectionAdapter adapter;
|
||||
private final NetworkTableConnection connection;
|
||||
|
||||
/**
|
||||
* create a new monitor thread
|
||||
* @param adapter
|
||||
* @param connection
|
||||
*/
|
||||
public ConnectionMonitorThread(final ConnectionAdapter adapter, final NetworkTableConnection connection) {
|
||||
this.adapter = adapter;
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
public void run() throws InterruptedException {
|
||||
try{
|
||||
connection.read(adapter);
|
||||
} catch(BadMessageException e){
|
||||
adapter.badMessage(e);
|
||||
} catch(IOException e){
|
||||
adapter.ioException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.connection;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.stream.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.type.*;
|
||||
|
||||
|
||||
/**
|
||||
* An abstraction for the NetworkTable protocol
|
||||
*
|
||||
* @author mwills
|
||||
*
|
||||
*/
|
||||
public class NetworkTableConnection {
|
||||
public static final char PROTOCOL_REVISION = 0x0200;
|
||||
|
||||
private final Object WRITE_LOCK = new Object();
|
||||
|
||||
private final DataInputStream is;
|
||||
private final DataOutputStream os;
|
||||
/**
|
||||
* the raw stream that is used in this connection
|
||||
*/
|
||||
public final IOStream stream;
|
||||
private final NetworkTableEntryTypeManager typeManager;
|
||||
private boolean isValid;
|
||||
|
||||
|
||||
public NetworkTableConnection(IOStream stream, NetworkTableEntryTypeManager typeManager){
|
||||
this.stream = stream;
|
||||
this.typeManager = typeManager;
|
||||
this.is = new DataInputStream(new BufferedInputStream(stream.getInputStream()));
|
||||
this.os = new DataOutputStream(new BufferedOutputStream(stream.getOutputStream()));
|
||||
isValid = true;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if(isValid){
|
||||
isValid = false;
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void sendMessageHeader(int messageType) throws IOException{
|
||||
synchronized(WRITE_LOCK){
|
||||
os.writeByte(messageType);
|
||||
}
|
||||
}
|
||||
public void flush() throws IOException{
|
||||
synchronized(WRITE_LOCK){
|
||||
os.flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void sendKeepAlive() throws IOException {
|
||||
synchronized(WRITE_LOCK){
|
||||
sendMessageHeader(NetworkTableMessageType.KEEP_ALIVE);
|
||||
flush();
|
||||
}
|
||||
}
|
||||
|
||||
public void sendClientHello() throws IOException {
|
||||
synchronized(WRITE_LOCK){
|
||||
sendMessageHeader(NetworkTableMessageType.CLIENT_HELLO);
|
||||
os.writeChar(PROTOCOL_REVISION);
|
||||
flush();
|
||||
}
|
||||
}
|
||||
|
||||
public void sendServerHelloComplete() throws IOException {
|
||||
synchronized(WRITE_LOCK){
|
||||
sendMessageHeader(NetworkTableMessageType.SERVER_HELLO_COMPLETE);
|
||||
flush();
|
||||
}
|
||||
}
|
||||
|
||||
public void sendProtocolVersionUnsupported() throws IOException {
|
||||
synchronized(WRITE_LOCK){
|
||||
sendMessageHeader(NetworkTableMessageType.PROTOCOL_VERSION_UNSUPPORTED);
|
||||
os.writeChar(PROTOCOL_REVISION);
|
||||
flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void sendEntryAssignment(NetworkTableEntry entry) throws IOException {
|
||||
synchronized(WRITE_LOCK){
|
||||
sendMessageHeader(NetworkTableMessageType.ENTRY_ASSIGNMENT);
|
||||
os.writeUTF(entry.name);
|
||||
os.writeByte(entry.getType().id);
|
||||
os.writeChar(entry.getId());
|
||||
os.writeChar(entry.getSequenceNumber());
|
||||
entry.sendValue(os);
|
||||
}
|
||||
}
|
||||
public void sendEntryUpdate(NetworkTableEntry entry) throws IOException {
|
||||
synchronized(WRITE_LOCK){
|
||||
sendMessageHeader(NetworkTableMessageType.FIELD_UPDATE);
|
||||
os.writeChar(entry.getId());
|
||||
os.writeChar(entry.getSequenceNumber());
|
||||
entry.sendValue(os);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void read(ConnectionAdapter adapter) throws IOException {
|
||||
try{
|
||||
int messageType = is.readByte();
|
||||
switch(messageType){
|
||||
case NetworkTableMessageType.KEEP_ALIVE:
|
||||
adapter.keepAlive();
|
||||
return;
|
||||
case NetworkTableMessageType.CLIENT_HELLO:
|
||||
{
|
||||
char protocolRevision = is.readChar();
|
||||
adapter.clientHello(protocolRevision);
|
||||
return;
|
||||
}
|
||||
case NetworkTableMessageType.SERVER_HELLO_COMPLETE:
|
||||
{
|
||||
adapter.serverHelloComplete();
|
||||
return;
|
||||
}
|
||||
case NetworkTableMessageType.PROTOCOL_VERSION_UNSUPPORTED:
|
||||
{
|
||||
char protocolRevision = is.readChar();
|
||||
adapter.protocolVersionUnsupported(protocolRevision);
|
||||
return;
|
||||
}
|
||||
case NetworkTableMessageType.ENTRY_ASSIGNMENT:
|
||||
{
|
||||
String entryName = is.readUTF();
|
||||
byte typeId = is.readByte();
|
||||
NetworkTableEntryType entryType = typeManager.getType(typeId);
|
||||
if(entryType==null)
|
||||
throw new BadMessageException("Unknown data type: 0x"+Integer.toHexString((int)typeId));
|
||||
char entryId = is.readChar();
|
||||
char entrySequenceNumber = is.readChar();
|
||||
Object value = entryType.readValue(is);
|
||||
adapter.offerIncomingAssignment(new NetworkTableEntry(entryId, entryName, entrySequenceNumber, entryType, value));
|
||||
return;
|
||||
}
|
||||
case NetworkTableMessageType.FIELD_UPDATE:
|
||||
{
|
||||
char entryId = is.readChar();
|
||||
char entrySequenceNumber = is.readChar();
|
||||
NetworkTableEntry entry = adapter.getEntry(entryId);
|
||||
if(entry==null)
|
||||
throw new BadMessageException("Received update for unknown entry id: "+(int)entryId);
|
||||
Object value = entry.getType().readValue(is);
|
||||
|
||||
adapter.offerIncomingUpdate(entry, entrySequenceNumber, value);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
throw new BadMessageException("Unknown Network Table Message Type: "+messageType);
|
||||
}
|
||||
} catch(IOException e){
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.server;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.stream.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.thread.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.type.*;
|
||||
|
||||
/**
|
||||
* A server node in NetworkTables 2.0
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class NetworkTableServer extends NetworkTableNode implements ServerIncomingConnectionListener{
|
||||
private final ServerIncomingStreamMonitor incomingStreamMonitor;
|
||||
private final WriteManager writeManager;
|
||||
private final IOStreamProvider streamProvider;
|
||||
private final ServerConnectionList connectionList;
|
||||
|
||||
/**
|
||||
* Create a NetworkTable Server
|
||||
*
|
||||
* @param streamProvider
|
||||
* @param threadManager
|
||||
* @param transactionPool
|
||||
*/
|
||||
public NetworkTableServer(final IOStreamProvider streamProvider, final NetworkTableEntryTypeManager typeManager, final NTThreadManager threadManager){
|
||||
ServerNetworkTableEntryStore entryStore;
|
||||
init(entryStore = new ServerNetworkTableEntryStore(this));
|
||||
this.streamProvider = streamProvider;
|
||||
|
||||
connectionList = new ServerConnectionList();
|
||||
writeManager = new WriteManager(connectionList, threadManager, getEntryStore(), Long.MAX_VALUE);
|
||||
|
||||
incomingStreamMonitor = new ServerIncomingStreamMonitor(streamProvider, entryStore, this, connectionList, typeManager, threadManager);
|
||||
|
||||
getEntryStore().setIncomingReceiver(new TransactionDirtier(writeManager));
|
||||
getEntryStore().setOutgoingReceiver(new TransactionDirtier(writeManager));
|
||||
|
||||
incomingStreamMonitor.start();
|
||||
writeManager.start();
|
||||
}
|
||||
/**
|
||||
* Create a NetworkTable Server
|
||||
*
|
||||
* @param streamProvider
|
||||
*/
|
||||
public NetworkTableServer(final IOStreamProvider streamProvider){
|
||||
this(streamProvider, new NetworkTableEntryTypeManager(), new DefaultThreadManager());
|
||||
}
|
||||
|
||||
public void close(){
|
||||
try {
|
||||
incomingStreamMonitor.stop();
|
||||
writeManager.stop();
|
||||
connectionList.closeAll();
|
||||
Thread.sleep(1000);//To get around bug where an error will occur in select if the socket server is closed before all sockets finish closing
|
||||
streamProvider.close();
|
||||
Thread.sleep(1000);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void onNewConnection(ServerConnectionAdapter connectionAdapter) {
|
||||
connectionList.add(connectionAdapter);
|
||||
}
|
||||
|
||||
|
||||
public boolean isConnected() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public boolean isServer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.server;
|
||||
|
||||
|
||||
/**
|
||||
* A class that manages connections to a server
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public interface ServerAdapterManager {
|
||||
/**
|
||||
* Called when a connection adapter has been closed
|
||||
* @param connectionAdapter the adapter that was closed
|
||||
*/
|
||||
public void close(ServerConnectionAdapter connectionAdapter, boolean closeStream);
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.server;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.connection.BadMessageException;
|
||||
import java.io.*;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.connection.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.stream.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.thread.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.type.*;
|
||||
|
||||
/**
|
||||
* Object that adapts messages from a client to the server
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class ServerConnectionAdapter implements ConnectionAdapter, IncomingEntryReceiver, FlushableOutgoingEntryReceiver{
|
||||
|
||||
private final ServerNetworkTableEntryStore entryStore;
|
||||
private final IncomingEntryReceiver transactionReceiver;
|
||||
private final ServerAdapterManager adapterListener;
|
||||
/**
|
||||
* the connection this adapter uses
|
||||
*/
|
||||
public final NetworkTableConnection connection;
|
||||
private final NTThread readThread;
|
||||
|
||||
private ServerConnectionState connectionState;
|
||||
private void gotoState(ServerConnectionState newState){
|
||||
if(connectionState!=newState){
|
||||
System.out.println(this+" entered connection state: "+newState);
|
||||
connectionState = newState;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a server connection adapter for a given stream
|
||||
*
|
||||
* @param stream
|
||||
* @param transactionPool
|
||||
* @param entryStore
|
||||
* @param transactionReceiver
|
||||
* @param adapterListener
|
||||
* @param threadManager
|
||||
*/
|
||||
public ServerConnectionAdapter(final IOStream stream, final ServerNetworkTableEntryStore entryStore, final IncomingEntryReceiver transactionReceiver, final ServerAdapterManager adapterListener, final NetworkTableEntryTypeManager typeManager, final NTThreadManager threadManager) {
|
||||
connection = new NetworkTableConnection(stream, typeManager);
|
||||
this.entryStore = entryStore;
|
||||
this.transactionReceiver = transactionReceiver;
|
||||
this.adapterListener = adapterListener;
|
||||
|
||||
gotoState(ServerConnectionState.GOT_CONNECTION_FROM_CLIENT);
|
||||
readThread = threadManager.newBlockingPeriodicThread(new ConnectionMonitorThread(this, connection), "Server Connection Reader Thread");
|
||||
}
|
||||
|
||||
|
||||
public void badMessage(BadMessageException e) {
|
||||
gotoState(new ServerConnectionState.Error(e));
|
||||
adapterListener.close(this, true);
|
||||
}
|
||||
public void ioException(IOException e) {
|
||||
if(e instanceof EOFException)
|
||||
gotoState(ServerConnectionState.CLIENT_DISCONNECTED);
|
||||
else
|
||||
gotoState(new ServerConnectionState.Error(e));
|
||||
adapterListener.close(this, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* stop the read thread and close the stream
|
||||
*/
|
||||
public void shutdown(boolean closeStream) {
|
||||
readThread.stop();
|
||||
if(closeStream)
|
||||
connection.close();
|
||||
}
|
||||
|
||||
public void keepAlive() throws IOException {
|
||||
//just let it happen
|
||||
}
|
||||
|
||||
public void clientHello(char protocolRevision) throws IOException {
|
||||
if(connectionState!=ServerConnectionState.GOT_CONNECTION_FROM_CLIENT)
|
||||
throw new BadMessageException("A server should not receive a client hello after it has already connected/entered an error state");
|
||||
if(protocolRevision!=NetworkTableConnection.PROTOCOL_REVISION){
|
||||
connection.sendProtocolVersionUnsupported();
|
||||
throw new BadMessageException("Client Connected with bad protocol revision: 0x"+Integer.toHexString(protocolRevision));
|
||||
}
|
||||
else{
|
||||
entryStore.sendServerHello(connection);
|
||||
gotoState(ServerConnectionState.CONNECTED_TO_CLIENT);
|
||||
}
|
||||
}
|
||||
|
||||
public void protocolVersionUnsupported(char protocolRevision) throws IOException {
|
||||
throw new BadMessageException("A server should not receive a protocol version unsupported message");
|
||||
}
|
||||
|
||||
public void serverHelloComplete() throws IOException {
|
||||
throw new BadMessageException("A server should not receive a server hello complete message");
|
||||
}
|
||||
|
||||
public void offerIncomingAssignment(NetworkTableEntry entry) {
|
||||
transactionReceiver.offerIncomingAssignment(entry);
|
||||
}
|
||||
|
||||
public void offerIncomingUpdate(NetworkTableEntry entry, char sequenceNumber, Object value) {
|
||||
transactionReceiver.offerIncomingUpdate(entry, sequenceNumber, value);
|
||||
}
|
||||
|
||||
public NetworkTableEntry getEntry(char id) {
|
||||
return entryStore.getEntry(id);
|
||||
}
|
||||
|
||||
public void offerOutgoingAssignment(NetworkTableEntry entry) {
|
||||
try {
|
||||
if(connectionState==ServerConnectionState.CONNECTED_TO_CLIENT)
|
||||
connection.sendEntryAssignment(entry);
|
||||
} catch (IOException e) {
|
||||
ioException(e);
|
||||
}
|
||||
}
|
||||
public void offerOutgoingUpdate(NetworkTableEntry entry) {
|
||||
try {
|
||||
if(connectionState==ServerConnectionState.CONNECTED_TO_CLIENT)
|
||||
connection.sendEntryUpdate(entry);
|
||||
} catch (IOException e) {
|
||||
ioException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void flush() {
|
||||
try {
|
||||
connection.flush();
|
||||
} catch (IOException e) {
|
||||
ioException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the state of the connection
|
||||
*/
|
||||
public ServerConnectionState getConnectionState() {
|
||||
return connectionState;
|
||||
}
|
||||
|
||||
public void ensureAlive() {
|
||||
try {
|
||||
connection.sendKeepAlive();
|
||||
} catch (IOException e) {
|
||||
ioException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.server;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.util.*;
|
||||
|
||||
/**
|
||||
* A list of connections that the server currently has
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class ServerConnectionList implements FlushableOutgoingEntryReceiver, ServerAdapterManager{
|
||||
private List connections = new List();
|
||||
private final Object connectionsLock = new Object();
|
||||
|
||||
/**
|
||||
* Add a connection to the list
|
||||
* @param connection
|
||||
*/
|
||||
public void add(final ServerConnectionAdapter connection){
|
||||
synchronized(connectionsLock){
|
||||
connections.add(connection);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void close(ServerConnectionAdapter connectionAdapter, boolean closeStream) {
|
||||
synchronized(connectionsLock){
|
||||
if(connections.remove(connectionAdapter)){
|
||||
System.out.println("Close: "+connectionAdapter);
|
||||
connectionAdapter.shutdown(closeStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* close all connections and remove them
|
||||
*/
|
||||
public void closeAll() {
|
||||
synchronized(connectionsLock){
|
||||
while(connections.size()>0){
|
||||
close((ServerConnectionAdapter)connections.get(0), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void offerOutgoingAssignment(NetworkTableEntry entry) {
|
||||
synchronized(connectionsLock){
|
||||
for(int i = 0; i<connections.size(); ++i){
|
||||
((ServerConnectionAdapter)connections.get(i)).offerOutgoingAssignment(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
public void offerOutgoingUpdate(NetworkTableEntry entry) {
|
||||
synchronized(connectionsLock){
|
||||
for(int i = 0; i<connections.size(); ++i){
|
||||
((ServerConnectionAdapter)connections.get(i)).offerOutgoingUpdate(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
public void flush() {
|
||||
synchronized(connectionsLock){
|
||||
for(int i = 0; i<connections.size(); ++i){//TODO iterate over as array
|
||||
((ServerConnectionAdapter)connections.get(i)).flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
public void ensureAlive() {
|
||||
synchronized(connectionsLock){
|
||||
for(int i = 0; i<connections.size(); ++i){//TODO iterate over as array
|
||||
((ServerConnectionAdapter)connections.get(i)).ensureAlive();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.server;
|
||||
|
||||
/**
|
||||
* Represents the state of a connection to the server
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class ServerConnectionState {
|
||||
/**
|
||||
* represents that the server has received the connection from the client but has not yet received the client hello
|
||||
*/
|
||||
public static final ServerConnectionState GOT_CONNECTION_FROM_CLIENT = new ServerConnectionState("GOT_CONNECTION_FROM_CLIENT");
|
||||
/**
|
||||
* represents that the client is in a connected non-error state
|
||||
*/
|
||||
public static final ServerConnectionState CONNECTED_TO_CLIENT = new ServerConnectionState("CONNECTED_TO_CLIENT");
|
||||
/**
|
||||
* represents that the client has disconnected from the server
|
||||
*/
|
||||
public static final ServerConnectionState CLIENT_DISCONNECTED = new ServerConnectionState("CLIENT_DISCONNECTED");
|
||||
|
||||
/**
|
||||
* Represents that the client is in an error state
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public static class Error extends ServerConnectionState{
|
||||
private final Exception e;
|
||||
/**
|
||||
* Create a new error state
|
||||
* @param e
|
||||
*/
|
||||
public Error(final Exception e){
|
||||
super("SERVER_ERROR");
|
||||
this.e = e;
|
||||
}
|
||||
public String toString(){
|
||||
return "SERVER_ERROR: "+e.getClass()+": "+e.getMessage();
|
||||
}
|
||||
/**
|
||||
* @return the exception that caused the client connection to enter an error state
|
||||
*/
|
||||
public Exception getException(){
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
private String name;
|
||||
protected ServerConnectionState(String name){
|
||||
this.name = name;
|
||||
}
|
||||
public String toString(){
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.server;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Listener for new incoming server connections
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public interface ServerIncomingConnectionListener {
|
||||
/**
|
||||
*
|
||||
* Called on create of a new connection
|
||||
* @param connectionAdapter the server connection adapter
|
||||
*/
|
||||
void onNewConnection(ServerConnectionAdapter connectionAdapter);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.server;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.stream.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.thread.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.type.*;
|
||||
|
||||
/**
|
||||
* Thread that monitors for incoming connections
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class ServerIncomingStreamMonitor implements PeriodicRunnable{
|
||||
|
||||
private final IOStreamProvider streamProvider;
|
||||
private final ServerIncomingConnectionListener incomingListener;
|
||||
private final ServerNetworkTableEntryStore entryStore;
|
||||
private final ServerAdapterManager adapterListener;
|
||||
|
||||
private NTThread monitorThread;
|
||||
private NTThreadManager threadManager;
|
||||
private final NetworkTableEntryTypeManager typeManager;
|
||||
|
||||
/**
|
||||
* Create a new incoming stream monitor
|
||||
* @param streamProvider the stream provider to retrieve streams from
|
||||
* @param entryStore the entry store for the server
|
||||
* @param transactionPool transaction pool for the server
|
||||
* @param incomingListener the listener that is notified of new connections
|
||||
* @param adapterListener the listener that will listen to adapter events
|
||||
* @param threadManager the thread manager used to create the incoming thread and provided to the Connection Adapters
|
||||
*/
|
||||
public ServerIncomingStreamMonitor(final IOStreamProvider streamProvider, final ServerNetworkTableEntryStore entryStore,
|
||||
final ServerIncomingConnectionListener incomingListener,
|
||||
final ServerAdapterManager adapterListener,
|
||||
final NetworkTableEntryTypeManager typeManager, final NTThreadManager threadManager) {
|
||||
this.streamProvider = streamProvider;
|
||||
this.entryStore = entryStore;
|
||||
this.incomingListener = incomingListener;
|
||||
this.adapterListener = adapterListener;
|
||||
this.typeManager = typeManager;
|
||||
this.threadManager = threadManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the monitor thread
|
||||
*/
|
||||
public void start(){
|
||||
if(monitorThread!=null)
|
||||
stop();
|
||||
monitorThread = threadManager.newBlockingPeriodicThread(this, "Server Incoming Stream Monitor Thread");
|
||||
}
|
||||
/**
|
||||
* Stop the monitor thread
|
||||
*/
|
||||
public void stop(){
|
||||
if(monitorThread!=null)
|
||||
monitorThread.stop();
|
||||
}
|
||||
|
||||
public void run(){
|
||||
IOStream newStream = null;
|
||||
try {
|
||||
newStream = streamProvider.accept();
|
||||
if(newStream!=null){
|
||||
ServerConnectionAdapter connectionAdapter = new ServerConnectionAdapter(newStream, entryStore, entryStore, adapterListener, typeManager, threadManager);
|
||||
incomingListener.onNewConnection(connectionAdapter);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
//could not get a new stream for some reason. ignore and continue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.server;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.*;
|
||||
import edu.wpi.first.wpilibj.networktables2.connection.*;
|
||||
|
||||
/**
|
||||
* The entry store for a {@link NetworkTableServer}
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class ServerNetworkTableEntryStore extends AbstractNetworkTableEntryStore{
|
||||
|
||||
/**
|
||||
* Create a new Server entry store
|
||||
* @param transactionPool the transaction pool
|
||||
* @param listenerManager the listener manager that fires events from this entry store
|
||||
*/
|
||||
public ServerNetworkTableEntryStore(final TableListenerManager listenerManager) {
|
||||
super(listenerManager);
|
||||
}
|
||||
|
||||
private char nextId = (char)0;
|
||||
protected boolean addEntry(NetworkTableEntry newEntry){
|
||||
synchronized(this){
|
||||
NetworkTableEntry entry = (NetworkTableEntry)namedEntries.get(newEntry.name);
|
||||
|
||||
if(entry==null){
|
||||
newEntry.setId(nextId++);
|
||||
idEntries.put(newEntry.getId(), newEntry);
|
||||
namedEntries.put(newEntry.name, newEntry);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean updateEntry(NetworkTableEntry entry, char sequenceNumber, Object value){
|
||||
synchronized(this){
|
||||
if(entry.putValue(sequenceNumber, value))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send all entries in the entry store as entry assignments in a single transaction
|
||||
* @param connection
|
||||
* @throws IOException
|
||||
*/
|
||||
void sendServerHello(final NetworkTableConnection connection) throws IOException {
|
||||
synchronized(this){
|
||||
Enumeration e = namedEntries.elements();
|
||||
while(e.hasMoreElements()){
|
||||
NetworkTableEntry entry = (NetworkTableEntry) e.nextElement();
|
||||
connection.sendEntryAssignment(entry);
|
||||
}
|
||||
connection.sendServerHelloComplete();
|
||||
connection.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.stream;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* An abstraction for a bidirectional stream that a network table can connect through
|
||||
*
|
||||
* @author mwills
|
||||
*
|
||||
*/
|
||||
public interface IOStream {
|
||||
|
||||
/**
|
||||
* @return the input stream for this IOStream
|
||||
*/
|
||||
public InputStream getInputStream();
|
||||
/**
|
||||
* @return the output stream for this IOStream
|
||||
*/
|
||||
public OutputStream getOutputStream();
|
||||
/**
|
||||
* completely close the stream
|
||||
*/
|
||||
public void close();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.stream;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* A factory that will create the same IOStream. A stream returned by this factory should be closed before calling createStream again
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public interface IOStreamFactory {
|
||||
/**
|
||||
* @return create a new stream
|
||||
* @throws IOException
|
||||
*/
|
||||
public IOStream createStream() throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.stream;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* An object that will provide the IOStream of clients to a NetworkTable Server
|
||||
*
|
||||
* @author mwills
|
||||
*
|
||||
*/
|
||||
public interface IOStreamProvider {
|
||||
/**
|
||||
*
|
||||
* @return a new IOStream normally from a server
|
||||
* @throws IOException
|
||||
*/
|
||||
IOStream accept() throws IOException;
|
||||
/**
|
||||
* Close the source of the IOStreams. {@link #accept()} should not be called after this is called
|
||||
* @throws IOException
|
||||
*/
|
||||
void close() throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.stream;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* An IOStream that wraps an {@link InputStream} and an {@link OutputStream}
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class SimpleIOStream implements IOStream{
|
||||
|
||||
private final InputStream is;
|
||||
private final OutputStream os;
|
||||
|
||||
/**
|
||||
* Create a new SimpleIOStream
|
||||
* @param is
|
||||
* @param os
|
||||
*/
|
||||
public SimpleIOStream(final InputStream is, final OutputStream os){
|
||||
this.is = is;
|
||||
this.os = os;
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
return is;
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream() {
|
||||
return os;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try{
|
||||
is.close();
|
||||
} catch(IOException e){
|
||||
//just ignore the error and close the output stream
|
||||
}
|
||||
try{
|
||||
os.close();
|
||||
} catch(IOException e){
|
||||
//just ignore the error and assume everything is now closed
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.thread;
|
||||
|
||||
/**
|
||||
* A simple thread manager that will run periodic threads in their own thread
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class DefaultThreadManager implements NTThreadManager {
|
||||
private static class PeriodicNTThread implements NTThread {
|
||||
private final Thread thread;
|
||||
private boolean run = true;
|
||||
public PeriodicNTThread(final PeriodicRunnable r, String name){
|
||||
thread = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
while(run){
|
||||
r.run();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
//Terminate thread when interrupted
|
||||
}
|
||||
}
|
||||
}, name);
|
||||
thread.start();
|
||||
}
|
||||
public void stop() {
|
||||
run = false;
|
||||
thread.interrupt();
|
||||
}
|
||||
public boolean isRunning() {
|
||||
return thread.isAlive();
|
||||
}
|
||||
}
|
||||
|
||||
public NTThread newBlockingPeriodicThread(final PeriodicRunnable r, String name) {
|
||||
return new PeriodicNTThread(r, name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.thread;
|
||||
|
||||
/**
|
||||
* Represents a thread in the network tables system
|
||||
* @author mwills
|
||||
*
|
||||
*/
|
||||
public interface NTThread {
|
||||
/**
|
||||
* stop the thread
|
||||
*/
|
||||
void stop();
|
||||
/**
|
||||
* @return true if the thread is running
|
||||
*/
|
||||
boolean isRunning();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.thread;
|
||||
|
||||
/**
|
||||
* A thread manager that can be used to obtain new threads
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public interface NTThreadManager {
|
||||
/**
|
||||
* @param r
|
||||
* @param name the name of the thread
|
||||
* @return a thread that will run the provided runnable repeatedly with the assumption that the runnable will block
|
||||
*/
|
||||
NTThread newBlockingPeriodicThread(PeriodicRunnable r, String name);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.thread;
|
||||
|
||||
/**
|
||||
* A runnable where the run method will be called periodically
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public interface PeriodicRunnable {
|
||||
/**
|
||||
* the method that will be called periodically on a thread
|
||||
*
|
||||
* @throws InterruptedException thrown when the thread is supposed to be interrupted and stop (implementers should always let this exception fall through)
|
||||
*/
|
||||
public void run() throws InterruptedException;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.type;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Mitchell
|
||||
*/
|
||||
public class ArrayData extends ComplexData{
|
||||
private final ArrayEntryType type;
|
||||
private Object[] data = new Object[0];
|
||||
public ArrayData(ArrayEntryType type){
|
||||
super(type);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
protected Object getAsObject(int index){
|
||||
return data[index];
|
||||
}
|
||||
protected void _set(int index, Object value){
|
||||
data[index] = value;
|
||||
}
|
||||
protected void _add(Object value){
|
||||
setSize(size()+1);
|
||||
data[size()-1] = value;
|
||||
}
|
||||
public void remove(int index){
|
||||
if(index<0 || index>=size())
|
||||
throw new IndexOutOfBoundsException();
|
||||
if(index < size()-1)
|
||||
System.arraycopy(data, index+1, data, index, size()-index-1);
|
||||
setSize(size()-1);
|
||||
}
|
||||
public void setSize(int size){
|
||||
if(size==data.length)
|
||||
return;
|
||||
Object[] newArray = new Object[size];//TODO cache arrays
|
||||
if(size<data.length)
|
||||
System.arraycopy(data, 0, newArray, 0, size);
|
||||
else{
|
||||
System.arraycopy(data, 0, newArray, 0, data.length);
|
||||
for(int i = data.length; i<newArray.length; ++i)
|
||||
newArray[i] = null;
|
||||
}
|
||||
data = newArray;
|
||||
}
|
||||
public int size(){
|
||||
return data.length;
|
||||
}
|
||||
|
||||
Object[] getDataArray() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.type;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.TableKeyExistsWithDifferentTypeException;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Mitchell
|
||||
*/
|
||||
public class ArrayEntryType extends ComplexEntryType {//TODO allow for array of complex type
|
||||
private final NetworkTableEntryType elementType;
|
||||
private final Class externalArrayType;
|
||||
|
||||
public ArrayEntryType(byte id, NetworkTableEntryType elementType, Class externalArrayType) {
|
||||
super(id, "Array of [" + elementType.name + "]");
|
||||
if(!ArrayData.class.isAssignableFrom(externalArrayType))
|
||||
throw new RuntimeException("External Array Data Type must extend ArrayData");
|
||||
this.externalArrayType = externalArrayType;
|
||||
this.elementType = elementType;
|
||||
}
|
||||
|
||||
public void sendValue(Object value, DataOutputStream os) throws IOException {
|
||||
if (value instanceof Object[]) {
|
||||
Object[] dataArray = (Object[]) value;
|
||||
if (dataArray.length > 255) {
|
||||
throw new IOException("Cannot write " + value + " as " + name + ". Arrays have a max length of 255 values");
|
||||
}
|
||||
os.writeByte(dataArray.length);
|
||||
for (int i = 0; i < dataArray.length; ++i) {
|
||||
elementType.sendValue(dataArray[i], os);
|
||||
}
|
||||
} else {
|
||||
throw new IOException("Cannot write " + value + " as " + name);
|
||||
}
|
||||
}
|
||||
|
||||
public Object readValue(DataInputStream is) throws IOException {
|
||||
int length = is.readUnsignedByte();
|
||||
Object[] dataArray = new Object[length];//TODO cache object arrays
|
||||
for (int i = 0; i < length; ++i) {
|
||||
dataArray[i] = elementType.readValue(is);
|
||||
}
|
||||
return dataArray;
|
||||
}
|
||||
|
||||
public Object internalizeValue(String key, Object externalRepresentation, Object currentInteralValue) {
|
||||
if (externalArrayType.isInstance(externalRepresentation)) {
|
||||
ArrayData externalArrayData = (ArrayData)externalRepresentation;
|
||||
Object[] internalArray;
|
||||
if(currentInteralValue instanceof Object[]
|
||||
&& (internalArray=((Object[])currentInteralValue)).length==externalArrayData.size()){
|
||||
System.arraycopy(externalArrayData.getDataArray(), 0, internalArray, 0, internalArray.length);
|
||||
return internalArray;
|
||||
}
|
||||
else{
|
||||
internalArray = new Object[externalArrayData.size()];
|
||||
System.arraycopy(externalArrayData.getDataArray(), 0, internalArray, 0, internalArray.length);
|
||||
return internalArray;
|
||||
}
|
||||
}
|
||||
throw new TableKeyExistsWithDifferentTypeException(key, this, externalRepresentation+" is not a "+externalArrayType);
|
||||
}
|
||||
|
||||
public void exportValue(String key, Object internalData, Object externalRepresentation) {
|
||||
if (!externalArrayType.isInstance(externalRepresentation))
|
||||
throw new TableKeyExistsWithDifferentTypeException(key, this, externalRepresentation+" is not a "+externalArrayType);
|
||||
if(!(internalData instanceof Object[]))
|
||||
throw new TableKeyExistsWithDifferentTypeException(key, this, "Internal data: "+internalData+" is not an array");
|
||||
|
||||
Object[] internalArray = (Object[])internalData;
|
||||
ArrayData externalArrayData = (ArrayData)externalRepresentation;
|
||||
externalArrayData.setSize(internalArray.length);
|
||||
System.arraycopy(internalArray, 0, externalArrayData.getDataArray(), 0, internalArray.length);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.type;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Mitchell
|
||||
*/
|
||||
public class BooleanArray extends ArrayData{
|
||||
|
||||
private static final byte BOOLEAN_ARRAY_RAW_ID = 0x10;
|
||||
public static final ArrayEntryType TYPE = new ArrayEntryType(BOOLEAN_ARRAY_RAW_ID, DefaultEntryTypes.BOOLEAN, BooleanArray.class);
|
||||
|
||||
|
||||
public BooleanArray(){
|
||||
super(TYPE);
|
||||
}
|
||||
|
||||
public boolean get(int index){
|
||||
return ((Boolean)getAsObject(index)).booleanValue();
|
||||
}
|
||||
public void set(int index, boolean value){
|
||||
_set(index, value?Boolean.TRUE:Boolean.FALSE);
|
||||
}
|
||||
public void add(boolean value){
|
||||
_add(value?Boolean.TRUE:Boolean.FALSE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.type;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Mitchell
|
||||
*/
|
||||
public class ComplexData {
|
||||
private final ComplexEntryType type;
|
||||
public ComplexData(ComplexEntryType type){
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public ComplexEntryType getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.type;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Mitchell
|
||||
*/
|
||||
public abstract class ComplexEntryType extends NetworkTableEntryType{
|
||||
|
||||
protected ComplexEntryType(byte id, String name){
|
||||
super(id, name);
|
||||
}
|
||||
|
||||
public abstract Object internalizeValue(String key, Object externalRepresentation, Object currentInteralValue);
|
||||
public abstract void exportValue(String key, Object internalData, Object externalRepresentation);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.type;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class DefaultEntryTypes {
|
||||
private static final byte BOOLEAN_RAW_ID = 0x00;
|
||||
private static final byte DOUBLE_RAW_ID = 0x01;
|
||||
private static final byte STRING_RAW_ID = 0x02;
|
||||
|
||||
/**
|
||||
* a boolean entry type
|
||||
*/
|
||||
public static final NetworkTableEntryType BOOLEAN = new NetworkTableEntryType(BOOLEAN_RAW_ID, "Boolean"){
|
||||
public void sendValue(Object value, DataOutputStream os) throws IOException {
|
||||
if(value instanceof Boolean)
|
||||
os.writeBoolean(((Boolean) value).booleanValue());
|
||||
else
|
||||
throw new IOException("Cannot write "+value+" as "+name);
|
||||
}
|
||||
public Object readValue(DataInputStream is) throws IOException {
|
||||
return is.readBoolean()?Boolean.TRUE:Boolean.FALSE;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* a double floating point type
|
||||
*/
|
||||
public static final NetworkTableEntryType DOUBLE = new NetworkTableEntryType(DOUBLE_RAW_ID, "Double"){
|
||||
public void sendValue(Object value, DataOutputStream os) throws IOException {
|
||||
if(value instanceof Double)
|
||||
os.writeDouble(((Double) value).doubleValue());
|
||||
else
|
||||
throw new IOException("Cannot write "+value+" as "+name);
|
||||
}
|
||||
public Object readValue(DataInputStream is) throws IOException {
|
||||
return new Double(is.readDouble());
|
||||
}
|
||||
};
|
||||
/**
|
||||
* a string type
|
||||
*/
|
||||
public static final NetworkTableEntryType STRING = new NetworkTableEntryType(STRING_RAW_ID, "String"){
|
||||
public void sendValue(Object value, DataOutputStream os) throws IOException {
|
||||
if(value instanceof String)
|
||||
os.writeUTF((String)value);
|
||||
else
|
||||
throw new IOException("Cannot write "+value+" as "+name);
|
||||
}
|
||||
public Object readValue(DataInputStream is) throws IOException {
|
||||
return is.readUTF();
|
||||
}
|
||||
};
|
||||
|
||||
public static void registerTypes(NetworkTableEntryTypeManager manager) {
|
||||
manager.registerType(BOOLEAN);
|
||||
manager.registerType(DOUBLE);
|
||||
manager.registerType(STRING);
|
||||
manager.registerType(BooleanArray.TYPE);
|
||||
manager.registerType(NumberArray.TYPE);
|
||||
manager.registerType(StringArray.TYPE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.type;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A class defining the types supported by NetworkTables as well as support for serialization of those types
|
||||
* to and from DataStreams
|
||||
*
|
||||
* @author mwills
|
||||
*
|
||||
*/
|
||||
public abstract class NetworkTableEntryType {
|
||||
|
||||
/**
|
||||
* the id of a type
|
||||
*/
|
||||
public final byte id;
|
||||
/**
|
||||
* the name of a type
|
||||
*/
|
||||
public final String name;
|
||||
protected NetworkTableEntryType(byte id, String name){
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
public String toString(){
|
||||
return "NetworkTable type: "+name;
|
||||
}
|
||||
|
||||
/**
|
||||
* send a value over a data output stream
|
||||
* @param value the value to send
|
||||
* @param os the stream to send the value over
|
||||
* @throws IOException an error occurred writing the value
|
||||
*/
|
||||
public abstract void sendValue(Object value, DataOutputStream os) throws IOException;
|
||||
/**
|
||||
* read a value from a data input stream
|
||||
* @param is the stream to read a value from
|
||||
* @return the value read from the stream
|
||||
* @throws IOException an error occurred reading the value
|
||||
*/
|
||||
public abstract Object readValue(DataInputStream is) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.type;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables2.util.*;
|
||||
|
||||
public class NetworkTableEntryTypeManager {
|
||||
private final ByteArrayMap typeMap = new ByteArrayMap();
|
||||
|
||||
public NetworkTableEntryType getType(byte id){
|
||||
return (NetworkTableEntryType)typeMap.get(id);
|
||||
}
|
||||
|
||||
void registerType(NetworkTableEntryType type){
|
||||
typeMap.put(type.id, type);
|
||||
}
|
||||
|
||||
public NetworkTableEntryTypeManager(){
|
||||
DefaultEntryTypes.registerTypes(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.type;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Mitchell
|
||||
*/
|
||||
public class NumberArray extends ArrayData{
|
||||
|
||||
private static final byte NUMBER_ARRAY_RAW_ID = 0x11;
|
||||
public static final ArrayEntryType TYPE = new ArrayEntryType(NUMBER_ARRAY_RAW_ID, DefaultEntryTypes.DOUBLE, NumberArray.class);
|
||||
|
||||
|
||||
public NumberArray(){
|
||||
super(TYPE);
|
||||
}
|
||||
|
||||
public double get(int index){
|
||||
return ((Double)getAsObject(index)).doubleValue();
|
||||
}
|
||||
public void set(int index, double value){
|
||||
_set(index, new Double(value));//TODO cache double values
|
||||
}
|
||||
public void add(double value){
|
||||
_add(new Double(value));//TODO cache double values
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.type;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Mitchell
|
||||
*/
|
||||
public class StringArray extends ArrayData{
|
||||
|
||||
private static final byte STRING_ARRAY_RAW_ID = 0x12;
|
||||
public static final ArrayEntryType TYPE = new ArrayEntryType(STRING_ARRAY_RAW_ID, DefaultEntryTypes.STRING, StringArray.class);
|
||||
|
||||
|
||||
public StringArray(){
|
||||
super(TYPE);
|
||||
}
|
||||
|
||||
public String get(int index){
|
||||
return ((String)getAsObject(index));
|
||||
}
|
||||
public void set(int index, String value){
|
||||
_set(index, value);
|
||||
}
|
||||
public void add(String value){
|
||||
_add(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.util;
|
||||
|
||||
/**
|
||||
* An unsynchronized map which maps bytes to objects. This map is backed by a array and will only perform well the values are consecutive starting at 0
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class ByteArrayMap extends ResizeableArrayObject{
|
||||
/**
|
||||
* Put a value with the given key
|
||||
* @param key
|
||||
* @param value
|
||||
*/
|
||||
public void put(final byte key, final Object value){
|
||||
int offsetKey = key+128;
|
||||
ensureSize(offsetKey+1);
|
||||
array[offsetKey] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @return the value associated with the given key or null if there is no value
|
||||
*/
|
||||
public Object get(final byte key){
|
||||
int offsetKey = key+128;
|
||||
if(offsetKey>=array.length)
|
||||
return null;
|
||||
return array[offsetKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* clear all values
|
||||
*/
|
||||
public void clear() {
|
||||
for(int i = 0; i<array.length; ++i)
|
||||
array[i] = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the value for the given key
|
||||
* @param key
|
||||
*/
|
||||
public void remove(final char key) {
|
||||
int offsetKey = key+128;
|
||||
if(offsetKey>=array.length)
|
||||
return;
|
||||
array[offsetKey] = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.util;
|
||||
|
||||
/**
|
||||
* An unsynchronized map which maps characters to objects. This map is backed by a array and will only perform well the values are consecutive starting at 0
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class CharacterArrayMap extends ResizeableArrayObject{
|
||||
/**
|
||||
* Put a value with the given key
|
||||
* @param key
|
||||
* @param value
|
||||
*/
|
||||
public void put(final char key, final Object value){
|
||||
ensureSize(key+1);
|
||||
array[key] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key
|
||||
* @return the value associated with the given key or null if there is no value
|
||||
*/
|
||||
public Object get(final char key){
|
||||
if(key>=array.length)
|
||||
return null;
|
||||
return array[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* clear all values
|
||||
*/
|
||||
public void clear() {
|
||||
for(int i = 0; i<array.length; ++i)
|
||||
array[i] = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the value for the given key
|
||||
* @param key
|
||||
*/
|
||||
public void remove(final char key) {
|
||||
if(key>=array.length)
|
||||
return;
|
||||
array[key] = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.util;
|
||||
|
||||
|
||||
/**
|
||||
* A queue designed to have things appended to it and then to be read directly from the backing array
|
||||
*
|
||||
* @author mwills
|
||||
*
|
||||
*/
|
||||
public class HalfQueue {
|
||||
public final Object[] array;
|
||||
private int size = 0;
|
||||
public HalfQueue(int maxSize){
|
||||
array = new Object[maxSize];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Push an element onto the stack
|
||||
* @param element
|
||||
*/
|
||||
public void queue(final Object element){
|
||||
array[size++] = element;
|
||||
}
|
||||
public boolean isFull(){
|
||||
return size==array.length;
|
||||
}
|
||||
public int size(){
|
||||
return size;
|
||||
}
|
||||
|
||||
public void clear(){
|
||||
size = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.util;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* A simple unsynchronized list implementation
|
||||
*
|
||||
* @author mwills
|
||||
*
|
||||
*/
|
||||
public class List extends ResizeableArrayObject{
|
||||
|
||||
protected int size = 0;
|
||||
/**
|
||||
* create a new list
|
||||
*/
|
||||
public List() {
|
||||
}
|
||||
/**
|
||||
* Create a new list with a given initial size of the backing array
|
||||
* @param initialSize the initial size of the backing array
|
||||
*/
|
||||
public List(final int initialSize) {
|
||||
super(initialSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the list is empty
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the size of the list
|
||||
*/
|
||||
public int size(){
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an object to the end of the list
|
||||
* @param o the object to be added
|
||||
*/
|
||||
public void add(final Object o){
|
||||
ensureSize(size+1);
|
||||
array[size++] = o;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an element from the list
|
||||
* @param index the index of the element to be removed
|
||||
* @throws IndexOutOfBoundsException if the index is not in the list
|
||||
*/
|
||||
public void remove(final int index) throws IndexOutOfBoundsException{
|
||||
if(index<0 || index>=size)
|
||||
throw new IndexOutOfBoundsException();
|
||||
if(index < size-1)
|
||||
System.arraycopy(array, index+1, array, index, size-index-1);
|
||||
size--;
|
||||
}
|
||||
|
||||
/**
|
||||
* clear all elements from the list
|
||||
*/
|
||||
public void clear(){
|
||||
size = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param index
|
||||
* @return a given element from the list
|
||||
*/
|
||||
public Object get(final int index) {
|
||||
if(index<0 || index>=size)
|
||||
throw new IndexOutOfBoundsException();
|
||||
return array[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* remove the first element in the list where {@link Object#equals(Object)} is true
|
||||
* @param object
|
||||
* @return true if the element was removed
|
||||
*/
|
||||
public boolean remove(final Object object) {
|
||||
for(int i = 0; i<size; ++i){
|
||||
Object value = array[i];
|
||||
if(object==null ? value==null : object.equals(value)){
|
||||
remove(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object
|
||||
* @return true if the list contains the given element (where {@link Object#equals(Object)} is true)
|
||||
*/
|
||||
public boolean contains(final Object object) {
|
||||
for(int i = 0; i<size; ++i){
|
||||
Object value = array[i];
|
||||
if(object==null ? value==null : object.equals(value)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the value at a given index in the list
|
||||
* @param index the index where the object should be put
|
||||
* @param obj the new value at the index
|
||||
*/
|
||||
public void set(final int index, final Object obj) {
|
||||
if(index<0 || index>=size)
|
||||
throw new IndexOutOfBoundsException();
|
||||
array[index] = obj;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.util;
|
||||
|
||||
/**
|
||||
* A helper super class for collections that use a resizing array
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class ResizeableArrayObject {
|
||||
private static final int GROW_FACTOR = 3;
|
||||
|
||||
protected Object[] array;
|
||||
|
||||
protected ResizeableArrayObject(){
|
||||
this(10);
|
||||
}
|
||||
protected ResizeableArrayObject(int initialSize){
|
||||
array = new Object[initialSize];
|
||||
}
|
||||
protected int arraySize(){
|
||||
return array.length;
|
||||
}
|
||||
protected void ensureSize(int size){
|
||||
if(size>array.length){
|
||||
int newSize = array.length;
|
||||
while(size>newSize)
|
||||
newSize *= GROW_FACTOR;
|
||||
Object[] newArray = new Object[newSize];
|
||||
System.arraycopy(array, 0, newArray, 0, array.length);
|
||||
array = newArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.util;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* A unsynchronized set
|
||||
*
|
||||
* @author mwills
|
||||
*
|
||||
*/
|
||||
public class Set extends List{
|
||||
|
||||
/**
|
||||
* Add an element to the set
|
||||
* @param o
|
||||
*/
|
||||
public void add(final Object o){
|
||||
if(!contains(o))
|
||||
super.add(o);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.util;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* A simple unsynchronized stack implementation
|
||||
*
|
||||
* @author mwills
|
||||
*
|
||||
*/
|
||||
public class Stack extends List{
|
||||
|
||||
/**
|
||||
* Push an element onto the stack
|
||||
* @param element
|
||||
*/
|
||||
public void push(final Object element){
|
||||
add(element);
|
||||
}
|
||||
/**
|
||||
* @return the element on the top of the stack and remove it
|
||||
*/
|
||||
public Object pop(){
|
||||
if(size==0)
|
||||
return null;
|
||||
Object value = get(size-1);
|
||||
remove(size-1);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package edu.wpi.first.wpilibj.networktables2.util;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A simple cache that allows for caching the mapping of one string to another calculated one
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public abstract class StringCache {
|
||||
private final Hashtable cache = new Hashtable();
|
||||
|
||||
|
||||
/**
|
||||
* @param input
|
||||
* @return the value for a given input
|
||||
*/
|
||||
public String get(final String input){
|
||||
String cachedValue = (String)cache.get(input);
|
||||
if(cachedValue==null)
|
||||
cache.put(input, cachedValue = calc(input));
|
||||
return cachedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will only be called if a value has not already been calculated
|
||||
* @param input
|
||||
* @return the calculated value for a given input
|
||||
*/
|
||||
public abstract String calc(String input);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package edu.wpi.first.wpilibj.tables;
|
||||
|
||||
|
||||
/**
|
||||
* Represents an object that has a remote connection
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public interface IRemote {
|
||||
/**
|
||||
* Register an object to listen for connection and disconnection events
|
||||
*
|
||||
* @param listener the listener to be register
|
||||
* @param immediateNotify if the listener object should be notified of the current connection state
|
||||
*/
|
||||
public void addConnectionListener(IRemoteConnectionListener listener, boolean immediateNotify);
|
||||
|
||||
/**
|
||||
* Unregister a listener from connection events
|
||||
*
|
||||
* @param listener the listener to be unregistered
|
||||
*/
|
||||
public void removeConnectionListener(IRemoteConnectionListener listener);
|
||||
|
||||
/**
|
||||
* Get the current state of the objects connection
|
||||
* @return the current connection state
|
||||
*/
|
||||
public boolean isConnected();
|
||||
|
||||
/**
|
||||
* If the object is acting as a server
|
||||
* @return if the object is a server
|
||||
*/
|
||||
public boolean isServer();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package edu.wpi.first.wpilibj.tables;
|
||||
|
||||
/**
|
||||
* A listener that listens for connection changes in a {@link IRemote} object
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public interface IRemoteConnectionListener {
|
||||
/**
|
||||
* Called when an IRemote is connected
|
||||
* @param remote the object that connected
|
||||
*/
|
||||
public void connected(IRemote remote);
|
||||
/**
|
||||
* Called when an IRemote is disconnected
|
||||
* @param remote the object that disconnected
|
||||
*/
|
||||
public void disconnected(IRemote remote);
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package edu.wpi.first.wpilibj.tables;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
|
||||
/**
|
||||
* A table whose values can be read and written to
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public interface ITable {
|
||||
|
||||
/**
|
||||
* @param key the key to search for
|
||||
* @return true if the table as a value assigned to the given key
|
||||
*/
|
||||
public boolean containsKey(String key);
|
||||
|
||||
/**
|
||||
* @param key the key to search for
|
||||
* @return true if there is a subtable with the key which contains at least one key/subtable of its own
|
||||
*/
|
||||
public boolean containsSubTable(String key);
|
||||
|
||||
/**
|
||||
* @param key the name of the table relative to this one
|
||||
* @return a sub table relative to this one
|
||||
*/
|
||||
public ITable getSubTable(String key);
|
||||
|
||||
|
||||
/**
|
||||
* Gets the value associated with a key as an object
|
||||
* @param key the key of the value to look up
|
||||
* @return the value associated with the given key
|
||||
* @throws TableKeyNotDefinedException if there is no value associated with the given key
|
||||
*/
|
||||
public Object getValue(String key) throws TableKeyNotDefinedException;
|
||||
/**
|
||||
* Put a value in the table
|
||||
* @param key the key to be assigned to
|
||||
* @param value the value that will be assigned
|
||||
* @throws IllegalArgumentException when the value is not supported by the table
|
||||
*/
|
||||
public void putValue(String key, Object value) throws IllegalArgumentException;
|
||||
|
||||
public void retrieveValue(String key, Object externalValue);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Put a number in the table
|
||||
* @param key the key to be assigned to
|
||||
* @param value the value that will be assigned
|
||||
*/
|
||||
public void putNumber(String key, double value);
|
||||
/**
|
||||
* @param key the key to look up
|
||||
* @return the value associated with the given key
|
||||
* @throws TableKeyNotDefinedException if there is no value associated with the given key
|
||||
*/
|
||||
public double getNumber(String key) throws TableKeyNotDefinedException;
|
||||
/**
|
||||
* @param key the key to look up
|
||||
* @param defaultValue the value to be returned if no value is found
|
||||
* @return the value associated with the given key or the given default value if there is no value associated with the key
|
||||
*/
|
||||
public double getNumber(String key, double defaultValue);
|
||||
|
||||
/**
|
||||
* Put a string in the table
|
||||
* @param key the key to be assigned to
|
||||
* @param value the value that will be assigned
|
||||
*/
|
||||
public void putString(String key, String value);
|
||||
/**
|
||||
* @param key the key to look up
|
||||
* @return the value associated with the given key
|
||||
* @throws TableKeyNotDefinedException if there is no value associated with the given key
|
||||
*/
|
||||
public String getString(String key) throws TableKeyNotDefinedException;
|
||||
/**
|
||||
* @param key the key to look up
|
||||
* @param defaultValue the value to be returned if no value is found
|
||||
* @return the value associated with the given key or the given default value if there is no value associated with the key
|
||||
*/
|
||||
public String getString(String key, String defaultValue);
|
||||
|
||||
/**
|
||||
* Put a boolean in the table
|
||||
* @param key the key to be assigned to
|
||||
* @param value the value that will be assigned
|
||||
*/
|
||||
public void putBoolean(String key, boolean value);
|
||||
/**
|
||||
* @param key the key to look up
|
||||
* @return the value associated with the given key
|
||||
* @throws TableKeyNotDefinedException if there is no value associated with the given key
|
||||
*/
|
||||
public boolean getBoolean(String key) throws TableKeyNotDefinedException;
|
||||
/**
|
||||
* @param key the key to look up
|
||||
* @param defaultValue the value to be returned if no value is found
|
||||
* @return the value associated with the given key or the given default value if there is no value associated with the key
|
||||
*/
|
||||
public boolean getBoolean(String key, boolean defaultValue);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Add a listener for changes to the table
|
||||
* @param listener the listener to add
|
||||
*/
|
||||
public void addTableListener(ITableListener listener);
|
||||
/**
|
||||
* Add a listener for changes to the table
|
||||
* @param listener the listener to add
|
||||
* @param immediateNotify if true then this listener will be notified of all current entries (marked as new)
|
||||
*/
|
||||
public void addTableListener(ITableListener listener, boolean immediateNotify);
|
||||
|
||||
/**
|
||||
* Add a listener for changes to a specific key the table
|
||||
* @param key the key to listen for
|
||||
* @param listener the listener to add
|
||||
* @param immediateNotify if true then this listener will be notified of all current entries (marked as new)
|
||||
*/
|
||||
public void addTableListener(String key, ITableListener listener, boolean immediateNotify);
|
||||
/**
|
||||
* This will immediately notify the listener of all current sub tables
|
||||
* @param listener
|
||||
*/
|
||||
public void addSubTableListener(final ITableListener listener);
|
||||
/**
|
||||
* Remove a listener from receiving table events
|
||||
* @param listener the listener to be removed
|
||||
*/
|
||||
public void removeTableListener(ITableListener listener);
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Depricated Methods
|
||||
*/
|
||||
/**
|
||||
* @deprecated
|
||||
* Maps the specified key to the specified value in this table.
|
||||
* The key can not be null.
|
||||
* The value can be retrieved by calling the get method with a key that is equal to the original key.
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @throws IllegalArgumentException if key is null
|
||||
*/
|
||||
public void putInt(String key, int value);
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Returns the value at the specified key.
|
||||
* @param key the key
|
||||
* @return the value
|
||||
* @throws TableKeyNotDefinedException if there is no value mapped to by the key
|
||||
* @throws IllegalArgumentException if the value mapped to by the key is not an int
|
||||
* @throws IllegalArgumentException if the key is null
|
||||
*/
|
||||
public int getInt(String key) throws TableKeyNotDefinedException;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Returns the value at the specified key.
|
||||
* @param key the key
|
||||
* @param defaultValue the value returned if the key is undefined
|
||||
* @return the value
|
||||
* @throws NetworkTableKeyNotDefined if there is no value mapped to by the key
|
||||
* @throws IllegalArgumentException if the value mapped to by the key is not an int
|
||||
* @throws IllegalArgumentException if the key is null
|
||||
*/
|
||||
public int getInt(String key, int defaultValue) throws TableKeyNotDefinedException;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Maps the specified key to the specified value in this table.
|
||||
* The key can not be null.
|
||||
* The value can be retrieved by calling the get method with a key that is equal to the original key.
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @throws IllegalArgumentException if key is null
|
||||
*/
|
||||
public void putDouble(String key, double value);
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Returns the value at the specified key.
|
||||
* @param key the key
|
||||
* @return the value
|
||||
* @throws NoSuchEleNetworkTableKeyNotDefinedmentException if there is no value mapped to by the key
|
||||
* @throws IllegalArgumentException if the value mapped to by the key is not a double
|
||||
* @throws IllegalArgumentException if the key is null
|
||||
*/
|
||||
public double getDouble(String key) throws TableKeyNotDefinedException;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Returns the value at the specified key.
|
||||
* @param key the key
|
||||
* @param defaultValue the value returned if the key is undefined
|
||||
* @return the value
|
||||
* @throws NoSuchEleNetworkTableKeyNotDefinedmentException if there is no value mapped to by the key
|
||||
* @throws IllegalArgumentException if the value mapped to by the key is not a double
|
||||
* @throws IllegalArgumentException if the key is null
|
||||
*/
|
||||
public double getDouble(String key, double defaultValue);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package edu.wpi.first.wpilibj.tables;
|
||||
|
||||
/**
|
||||
* A listener that listens to changes in values in a {@link ITable}
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public interface ITableListener {
|
||||
/**
|
||||
* Called when a key-value pair is changed in a {@link ITable}
|
||||
* WARNING: If a new key-value is put in this method value changed will immediatly be called which could lead to recursive code
|
||||
* @param source the table the key-value pair exists in
|
||||
* @param key the key associated with the value that changed
|
||||
* @param value the new value
|
||||
* @param isNew true if the key did not previously exist in the table, otherwise it is false
|
||||
*/
|
||||
public void valueChanged(ITable source, String key, Object value, boolean isNew);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package edu.wpi.first.wpilibj.tables;
|
||||
|
||||
/**
|
||||
* A simple interface to provide tables
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public interface ITableProvider {
|
||||
/**
|
||||
* Get a table by name
|
||||
* @param name the name of the table
|
||||
* @return a Table with the given name
|
||||
*/
|
||||
public ITable getTable(String name);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package edu.wpi.first.wpilibj.tables;
|
||||
|
||||
import edu.wpi.first.wpilibj.networktables.NetworkTableKeyNotDefined;
|
||||
|
||||
/**
|
||||
* An exception throw when the lookup a a key-value fails in a {@link ITable}
|
||||
*
|
||||
* @author Mitchell
|
||||
*
|
||||
*/
|
||||
public class TableKeyNotDefinedException extends NetworkTableKeyNotDefined {
|
||||
|
||||
/**
|
||||
* @param key the key that was not defined in the table
|
||||
*/
|
||||
public TableKeyNotDefinedException(String key) {
|
||||
super(key);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user