2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.lutron.internal.net;
15 import java.io.BufferedReader;
16 import java.io.Closeable;
17 import java.io.IOException;
18 import java.io.InputStreamReader;
19 import java.io.PrintStream;
20 import java.nio.CharBuffer;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collections;
24 import java.util.List;
25 import java.util.regex.MatchResult;
26 import java.util.regex.Matcher;
27 import java.util.regex.Pattern;
29 import org.apache.commons.net.telnet.InvalidTelnetOptionException;
30 import org.apache.commons.net.telnet.SuppressGAOptionHandler;
31 import org.apache.commons.net.telnet.TelnetClient;
32 import org.apache.commons.net.telnet.TelnetInputListener;
33 import org.apache.commons.net.telnet.TelnetOptionHandler;
34 import org.eclipse.jdt.annotation.NonNullByDefault;
35 import org.eclipse.jdt.annotation.Nullable;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
40 * A single telnet session.
42 * @author Allan Tong - Initial contribution
43 * @author Bob Adair - Fix to readInput and added debug logging
46 public class TelnetSession implements Closeable {
48 private static final int BUFSIZE = 8192;
50 private final Logger logger = LoggerFactory.getLogger(TelnetSession.class);
52 private TelnetClient telnetClient;
53 private @Nullable BufferedReader reader;
54 private @Nullable PrintStream outstream;
56 private CharBuffer charBuffer;
57 private List<TelnetSessionListener> listeners = new ArrayList<>();
59 private @Nullable TelnetOptionHandler suppressGAOptionHandler;
61 public TelnetSession() {
62 logger.trace("Creating new TelnetSession");
63 this.telnetClient = new TelnetClient();
64 this.charBuffer = CharBuffer.allocate(BUFSIZE);
66 this.telnetClient.setReaderThread(true);
67 this.telnetClient.registerInputListener(new TelnetInputListener() {
69 public void telnetInputAvailable() {
72 } catch (IOException e) {
79 public void addListener(TelnetSessionListener listener) {
80 this.listeners.add(listener);
83 public void clearListeners() {
84 this.listeners.clear();
87 private void notifyInputAvailable() {
88 for (TelnetSessionListener listener : this.listeners) {
89 listener.inputAvailable();
93 private void notifyInputError(IOException exception) {
94 logger.debug("TelnetSession notifyInputError: {}", exception.getMessage());
95 for (TelnetSessionListener listener : this.listeners) {
96 listener.error(exception);
100 public void open(String host) throws IOException {
104 public void open(String host, int port) throws IOException {
105 // Synchronized block prevents listener thread from attempting to read input before we're ready.
106 synchronized (this.charBuffer) {
107 logger.trace("TelnetSession open called");
109 telnetClient.connect(host, port);
110 telnetClient.setKeepAlive(true);
111 } catch (IOException e) {
112 logger.debug("TelnetSession open: error connecting: {}", e.getMessage());
116 if (this.suppressGAOptionHandler == null) {
117 // Only do this once.
118 this.suppressGAOptionHandler = new SuppressGAOptionHandler(true, true, true, true);
121 this.telnetClient.addOptionHandler(this.suppressGAOptionHandler);
122 } catch (InvalidTelnetOptionException e) {
123 // Should never happen. Wrap it inside IOException so as not to declare another throwable.
124 logger.debug("TelnetSession open: error adding telnet option handler: {}", e.getMessage());
125 throw new IOException(e);
129 this.reader = new BufferedReader(new InputStreamReader(this.telnetClient.getInputStream()));
130 this.outstream = new PrintStream(this.telnetClient.getOutputStream());
135 public void close() throws IOException {
136 synchronized (charBuffer) {
137 logger.trace("TelnetSession close called");
139 if (telnetClient.isConnected()) {
140 telnetClient.disconnect();
142 } catch (IOException e) {
143 logger.debug("TelnetSession close: error disconnecting: {}", e.getMessage());
152 public boolean isConnected() {
153 synchronized (charBuffer) {
154 return reader != null;
158 private void readInput() throws IOException {
159 synchronized (charBuffer) {
160 if (reader != null) {
162 reader.read(charBuffer);
163 } catch (IOException e) {
164 logger.debug("TelnetSession readInput: error reading: {}", e.getMessage());
167 charBuffer.notifyAll();
169 if (charBuffer.position() > 0) {
170 notifyInputAvailable();
173 logger.debug("TelnetSession readInput: reader is null - session is closed");
174 throw new IOException("Session is closed");
179 public MatchResult waitFor(String prompt) throws InterruptedException {
180 return waitFor(prompt, 0);
183 public MatchResult waitFor(String prompt, long timeout) throws InterruptedException {
184 Pattern regex = Pattern.compile(prompt);
185 long startTime = timeout > 0 ? System.currentTimeMillis() : 0;
187 logger.trace("TelnetSession waitFor called with {} {}", prompt, timeout);
188 synchronized (this.charBuffer) {
189 this.charBuffer.flip();
191 String bufdata = this.charBuffer.toString();
192 int n = bufdata.lastIndexOf('\n');
196 lastLine = bufdata.substring(n + 1);
201 Matcher matcher = regex.matcher(lastLine);
203 while (!matcher.find()) {
204 long elapsed = timeout > 0 ? (System.currentTimeMillis() - startTime) : 0;
206 if (timeout > 0 && elapsed >= timeout) {
210 this.charBuffer.clear();
211 this.charBuffer.put(lastLine);
213 this.charBuffer.wait(timeout - elapsed);
214 this.charBuffer.flip();
216 bufdata = this.charBuffer.toString();
217 n = bufdata.lastIndexOf('\n');
220 lastLine = bufdata.substring(n + 1);
225 matcher = regex.matcher(lastLine);
228 this.charBuffer.clear();
230 return matcher.toMatchResult();
234 public Iterable<String> readLines() {
235 synchronized (this.charBuffer) {
236 this.charBuffer.flip();
238 String bufdata = this.charBuffer.toString();
239 int n = bufdata.lastIndexOf('\n');
241 String[] lines = null;
244 leftover = bufdata.substring(n + 1);
245 bufdata = bufdata.substring(0, n).trim();
247 lines = bufdata.split("\r\n");
252 this.charBuffer.clear();
253 this.charBuffer.put(leftover);
255 return lines == null ? Collections.<String> emptyList() : Arrays.asList(lines);
259 public void writeLine(String line) throws IOException {
260 synchronized (charBuffer) {
261 logger.trace("TelnetSession writeLine called with {}", line);
262 PrintStream out = outstream;
264 logger.debug("TelnetSession writeLine: outstream is null - session is closed");
265 throw new IOException("Session is closed");
267 out.print(line + "\r\n");
269 if (out.checkError()) {
270 logger.debug("TelnetSession writeLine: error writing to outstream");
271 throw new IOException("Could not write to stream");