001package edu.wpi.first.wpilibj.networktables2.client;
002
003import edu.wpi.first.wpilibj.networktables2.connection.BadMessageException;
004import java.io.*;
005
006import edu.wpi.first.wpilibj.networktables2.*;
007import edu.wpi.first.wpilibj.networktables2.connection.*;
008import edu.wpi.first.wpilibj.networktables2.stream.*;
009import edu.wpi.first.wpilibj.networktables2.thread.*;
010import edu.wpi.first.wpilibj.networktables2.type.*;
011
012/**
013 * Object that adapts messages from a server
014 * 
015 * @author Mitchell
016 *
017 */
018public class ClientConnectionAdapter implements ConnectionAdapter, IncomingEntryReceiver, FlushableOutgoingEntryReceiver{
019        
020        private final ClientNetworkTableEntryStore entryStore;
021        private final IOStreamFactory streamFactory;
022        private final NTThreadManager threadManager;
023        
024        private NetworkTableConnection connection;
025        private NTThread readThread;
026        private ClientConnectionState connectionState = ClientConnectionState.DISCONNECTED_FROM_SERVER;
027        private final ClientConnectionListenerManager connectionListenerManager;
028        private final Object connectionLock = new Object();
029        private final NetworkTableEntryTypeManager typeManager;
030
031        private void gotoState(ClientConnectionState newState){
032                synchronized(connectionLock){
033                        if(connectionState!=newState){
034                                System.out.println(this+" entered connection state: "+newState);
035                                if(newState==ClientConnectionState.IN_SYNC_WITH_SERVER)
036                                        connectionListenerManager.fireConnectedEvent();
037                                if(connectionState==ClientConnectionState.IN_SYNC_WITH_SERVER)
038                                        connectionListenerManager.fireDisconnectedEvent();
039                                connectionState = newState;
040                        }
041                }
042        }
043        /**
044         * @return the state of the connection
045         */
046        public ClientConnectionState getConnectionState(){
047                return connectionState;
048        }
049        /**
050         * @return if the client is connected to the server
051         */
052        public boolean isConnected() {
053                return getConnectionState()==ClientConnectionState.IN_SYNC_WITH_SERVER;
054        }
055
056        /**
057         * Create a new ClientConnectionAdapter
058         * @param entryStore
059         * @param threadManager
060         * @param streamFactory
061         * @param transactionPool
062         * @param connectionListenerManager
063         */
064        public ClientConnectionAdapter(final ClientNetworkTableEntryStore entryStore, final NTThreadManager threadManager, final IOStreamFactory streamFactory, final ClientConnectionListenerManager connectionListenerManager, final NetworkTableEntryTypeManager typeManager) {
065                this.entryStore = entryStore;
066                this.streamFactory = streamFactory;
067                this.threadManager = threadManager;
068                this.connectionListenerManager = connectionListenerManager;
069                this.typeManager = typeManager;
070        }
071        
072        
073        
074        /*
075         * Connection management
076         */
077        /**
078         * Reconnect the client to the server (even if the client is not currently connected)
079         */
080        public void reconnect() {
081                synchronized(connectionLock){
082                        close();//close the existing stream and monitor thread if needed
083                        try{
084                                IOStream stream = streamFactory.createStream();
085                                if(stream==null)
086                                        return;
087                                connection = new NetworkTableConnection(stream, typeManager);
088                                readThread = threadManager.newBlockingPeriodicThread(new ConnectionMonitorThread(this, connection), "Client Connection Reader Thread");
089                                connection.sendClientHello();
090                                gotoState(ClientConnectionState.CONNECTED_TO_SERVER);
091                        } catch(Exception e){
092                                close();//make sure to clean everything up if we fail to connect
093                        }
094                }
095        }
096        
097        /**
098         * Close the client connection
099         */
100        public void close() {
101                close(ClientConnectionState.DISCONNECTED_FROM_SERVER);
102        }
103        /**
104         * Close the connection to the server and enter the given state
105         * @param newState
106         */
107        public void close(final ClientConnectionState newState) {
108                synchronized(connectionLock){
109                        gotoState(newState);
110                        if(readThread!=null){
111                                readThread.stop();
112                                readThread = null;
113                        }
114                        if(connection!=null){
115                                connection.close();
116                                connection = null;
117                        }
118                        entryStore.clearIds();
119                }
120        }
121        
122        
123
124        public void badMessage(BadMessageException e) {
125                close(new ClientConnectionState.Error(e));
126        }
127
128        public void ioException(IOException e) {
129                if(connectionState!=ClientConnectionState.DISCONNECTED_FROM_SERVER)//will get io exception when on read thread connection is closed
130                        reconnect();
131                //gotoState(new ClientConnectionState.Error(e));
132        }
133        
134        public NetworkTableEntry getEntry(char id) {
135                return entryStore.getEntry(id);
136        }
137        
138        
139        public void keepAlive() throws IOException {
140        }
141
142        public void clientHello(char protocolRevision) throws IOException {
143                throw new BadMessageException("A client should not receive a client hello message");
144        }
145
146        public void protocolVersionUnsupported(char protocolRevision) {
147                close();
148                gotoState(new ClientConnectionState.ProtocolUnsuppotedByServer(protocolRevision));
149        }
150
151        public void serverHelloComplete() throws IOException {
152                if (connectionState==ClientConnectionState.CONNECTED_TO_SERVER) {
153                        try {
154                                gotoState(ClientConnectionState.IN_SYNC_WITH_SERVER);
155                                entryStore.sendUnknownEntries(connection);
156                        } catch (IOException e) {
157                                ioException(e);
158                        }
159                }
160                else
161                        throw new BadMessageException("A client should only receive a server hello complete once and only after it has connected to the server");
162        }
163
164
165        public void offerIncomingAssignment(NetworkTableEntry entry) {
166                entryStore.offerIncomingAssignment(entry);
167        }
168        public void offerIncomingUpdate(NetworkTableEntry entry, char sequenceNumber, Object value) {
169                entryStore.offerIncomingUpdate(entry, sequenceNumber, value);
170        }
171
172        public void offerOutgoingAssignment(NetworkTableEntry entry) {
173                try {
174                        synchronized(connectionLock){
175                                if(connection!=null && connectionState==ClientConnectionState.IN_SYNC_WITH_SERVER)
176                                        connection.sendEntryAssignment(entry);
177                        }
178                } catch(IOException e){
179                        ioException(e);
180                }
181        }
182
183        public void offerOutgoingUpdate(NetworkTableEntry entry) {
184                try {
185                        synchronized(connectionLock){
186                                if(connection!=null && connectionState==ClientConnectionState.IN_SYNC_WITH_SERVER)
187                                        connection.sendEntryUpdate(entry);
188                        }
189                } catch(IOException e){
190                        ioException(e);
191                }
192        }
193        public void flush() {
194                synchronized(connectionLock){
195                        if(connection!=null) {
196                                try {
197                                        connection.flush();
198                                } catch (IOException e) {
199                                        ioException(e);
200                                }
201                        }
202                }
203        }
204        public void ensureAlive() {
205                synchronized(connectionLock){
206                        if(connection!=null) {
207                                try {
208                                        connection.sendKeepAlive();
209                                } catch (IOException e) {
210                                        ioException(e);
211                                }
212                        }
213                        else
214                                reconnect();//try to reconnect if not connected
215                }
216        }
217
218}