View Javadoc
1   /*
2    * Licensed under the GPL License. You may not use this file except in compliance with the License.
3    * You may obtain a copy of the License at
4    *
5    *   https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
6    *
7    * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
8    * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
9    * PURPOSE.
10   */
11  package psiprobe.tools;
12  
13  import java.io.IOException;
14  import java.net.Socket;
15  import java.util.concurrent.locks.ReentrantLock;
16  
17  import org.slf4j.Logger;
18  import org.slf4j.LoggerFactory;
19  
20  /**
21   * A factory for creating AsyncSocket objects.
22   */
23  public final class AsyncSocketFactory {
24  
25    /** The Constant logger. */
26    private static final Logger logger = LoggerFactory.getLogger(AsyncSocketFactory.class);
27  
28    /**
29     * Prevent Instantiation.
30     */
31    private AsyncSocketFactory() {
32      // Prevent Instantiation
33    }
34  
35    /**
36     * Creates a new AsyncSocket object.
37     *
38     * @param server the server
39     * @param port the port
40     * @param timeout the timeout
41     *
42     * @return the socket
43     *
44     * @throws IOException Signals that an I/O exception has occurred.
45     */
46    public static Socket createSocket(String server, int port, long timeout) throws IOException {
47      SocketWrapper socketWrapper = new SocketWrapper();
48      socketWrapper.server = server;
49      socketWrapper.port = port;
50  
51      ReentrantLock sync = new ReentrantLock();
52      Thread socketThread = new Thread(new SocketRunnable(socketWrapper, sync));
53      socketThread.setDaemon(true);
54      Thread timeoutThread = new Thread(new TimeoutRunnable(sync, timeout * 1000));
55      timeoutThread.setDaemon(true);
56  
57      timeoutThread.start();
58      socketThread.start();
59  
60      sync.lock();
61      try {
62        if (socketWrapper.socket == null) {
63          boolean inProgress = true;
64          while (inProgress) {
65            try {
66              sync.wait(timeout * 1000);
67            } catch (InterruptedException e) {
68              // Restore interrupted state...
69              Thread.currentThread().interrupt();
70              logger.trace("", e);
71            }
72            inProgress = false;
73          }
74        }
75      } finally {
76        sync.unlock();
77      }
78  
79      timeoutThread.interrupt();
80      socketThread.interrupt();
81  
82      socketWrapper.valid = false;
83  
84      if (socketWrapper.socket == null && socketWrapper.exception != null) {
85        throw socketWrapper.exception;
86      }
87      if (socketWrapper.socket == null) {
88        throw new TimeoutException();
89      }
90  
91      return socketWrapper.getSocket();
92    }
93  
94    /**
95     * The Class SocketWrapper.
96     */
97    static final class SocketWrapper {
98  
99      /** The socket. */
100     Socket socket;
101 
102     /** The server. */
103     String server;
104 
105     /** The port. */
106     int port;
107 
108     /** The exception. */
109     IOException exception;
110 
111     /** The valid. */
112     boolean valid = true;
113 
114     /**
115      * Gets the socket.
116      *
117      * @return the socket
118      */
119     public Socket getSocket() {
120       return socket;
121     }
122 
123     /**
124      * Sets the socket.
125      *
126      * @param socket the new socket
127      */
128     public void setSocket(Socket socket) {
129       this.socket = socket;
130     }
131 
132     /**
133      * Gets the server.
134      *
135      * @return the server
136      */
137     public String getServer() {
138       return server;
139     }
140 
141     /**
142      * Gets the port.
143      *
144      * @return the port
145      */
146     public int getPort() {
147       return port;
148     }
149 
150     /**
151      * Sets the exception.
152      *
153      * @param exception the new exception
154      */
155     public void setException(IOException exception) {
156       this.exception = exception;
157     }
158 
159     /**
160      * Checks if is valid.
161      *
162      * @return true, if is valid
163      */
164     public boolean isValid() {
165       return valid;
166     }
167 
168     /**
169      * Instantiates a new socket wrapper.
170      */
171     private SocketWrapper() {}
172 
173   }
174 
175   /**
176    * The Class SocketRunnable.
177    */
178   static final class SocketRunnable implements Runnable {
179 
180     /** The socket wrapper. */
181     private final SocketWrapper socketWrapper;
182 
183     /** The sync. */
184     private final ReentrantLock sync;
185 
186     /**
187      * Instantiates a new socket runnable.
188      *
189      * @param socketWrapper the socket wrapper
190      * @param sync the sync
191      */
192     private SocketRunnable(SocketWrapper socketWrapper, ReentrantLock sync) {
193       this.socketWrapper = socketWrapper;
194       this.sync = sync;
195     }
196 
197     @Override
198     public void run() {
199       try (Socket socket = new Socket(socketWrapper.getServer(), socketWrapper.getPort())) {
200         socketWrapper.setSocket(socket);
201         if (!socketWrapper.isValid()) {
202           socketWrapper.getSocket().close();
203           socketWrapper.setSocket(null);
204         }
205       } catch (IOException e) {
206         logger.trace("", e);
207         socketWrapper.setException(e);
208       }
209       sync.lock();
210       try {
211         sync.notifyAll();
212       } finally {
213         sync.unlock();
214       }
215     }
216 
217   }
218 
219   /**
220    * The Class TimeoutRunnable.
221    */
222   static final class TimeoutRunnable implements Runnable {
223 
224     /** The sync. */
225     private final ReentrantLock sync;
226 
227     /** The timeout. */
228     private final long timeout;
229 
230     /**
231      * Instantiates a new timeout runnable.
232      *
233      * @param sync the sync
234      * @param timeout the timeout
235      */
236     private TimeoutRunnable(ReentrantLock sync, long timeout) {
237       this.sync = sync;
238       this.timeout = timeout;
239     }
240 
241     @Override
242     public void run() {
243       try {
244         Thread.sleep(timeout);
245         sync.lock();
246         try {
247           sync.notifyAll();
248         } finally {
249           sync.unlock();
250         }
251       } catch (InterruptedException e) {
252         // Restore interrupted state...
253         Thread.currentThread().interrupt();
254         logger.trace("", e);
255       }
256     }
257 
258   }
259 
260 }