2 * Copyright 2001 Sun Microsystems, Inc.
4 * See the file "license.terms" for information on usage and
5 * redistribution of this file, and for a DISCLAIMER OF ALL
8 import com.sun.speech.freetts.util.Utilities;
10 import java.io.BufferedReader;
11 import java.io.DataOutputStream;
12 import java.io.IOException;
13 import java.io.InputStreamReader;
14 import java.io.OutputStream;
16 import java.net.Socket;
17 import java.net.SocketTimeoutException;
21 * Implements a very simplified (and incomplete) version of the
22 * Emacspeak speech server.
24 * See the Emacspeak protocol document at
25 * http://emacspeak.sourceforge.net/info/html/TTS-Servers.html
26 * for more information.
28 public abstract class EmacspeakProtocolHandler implements Runnable {
30 // network related variables
31 private Socket socket;
32 private BufferedReader reader;
33 private OutputStream writer;
35 // synthesizer related variables
36 protected static final String PARENS_STAR_REGEX = "[.]*\\[\\*\\][.]*";
37 private static final int NOT_HANDLED_COMMAND = 1;
38 private static final int LETTER_COMMAND = 2;
39 private static final int QUEUE_COMMAND = 3;
40 private static final int TTS_SAY_COMMAND = 4;
41 private static final int STOP_COMMAND = 5;
42 private static final int EXIT_COMMAND = 6;
43 private static final int RATE_COMMAND = 7;
45 private String lastQueuedCommand;
46 private String stopQuestionStart = "Active processes exist;";
48 private boolean debug = false;
51 * Sometimes emacspeak will embed DECTalk escape sequences in the
52 * text. These sequences are not meant to be spoken, and FreeTTS
53 * currently does not interpret them. This simple flag provides
54 * a mechanism for FreeTTS to cut strings of the form "[...]"
55 * out of text to be spoken (DECTalk escape sequences are of the
56 * form "[...]"). Since this is a relatively heavy-handed thing
57 * to do, this feature is turned off by default. To turn it on,
58 * add -DstripDECTalk=true to the command line.
60 private boolean stripDECTalk = false;
63 * Sets the Socket to be used by this ProtocolHandler.
65 * @param socket the Socket to be used
67 public void setSocket(Socket socket) {
71 reader = new BufferedReader
72 (new InputStreamReader(socket.getInputStream()));
73 writer = new DataOutputStream(socket.getOutputStream());
74 socket.setKeepAlive(true);
75 // socket.setSoTimeout(5000);
76 } catch (IOException ioe) {
77 ioe.printStackTrace();
85 * Returns the socket used.
87 * @return the socket used
89 public Socket getSocket() {
95 * Set to debug mode, which will print out debug messages.
97 * @param true if set to debug mode, false if set to non-debug mode.
99 public void setDebug(boolean debug) {
105 * Returns true if the given input string starts with the given
106 * starting and ending sequence.
108 * @param start the starting character sequence
109 * @param end the ending character sequence
111 * @return true if the input string matches the given Pattern;
114 private static boolean matches(String start, String end, String input) {
115 return (input.startsWith(start) && input.endsWith(end));
120 * Returns the type of the given command.
122 * @param command the command from emacspeak
124 * @return the command type
126 private static int getCommandType(String command) {
127 int type = NOT_HANDLED_COMMAND;
128 if (command.startsWith("l ")) {
129 type = LETTER_COMMAND;
130 } else if (command.startsWith("q ")) {
131 type = QUEUE_COMMAND;
132 } else if (command.startsWith("tts_say ")) {
133 type = TTS_SAY_COMMAND;
134 } else if (command.startsWith("tts_set_speech_rate ")) {
136 } else if (command.equals("s")) {
138 } else if (command.equals("exit")) {
146 * Returns the text of the given input that is within curly
147 * brackets. If there are no curly brackets (allowed in the
148 * Emacspeak protocol if the text has no spaces), then it
149 * just returns the text after the first space.
151 * @param input the input text
153 * @return text within curly brackets
155 public static String textInCurlyBrackets(String input) {
157 if (input.length() > 0) {
158 int first = input.indexOf('{');
160 first = input.indexOf(' ');
162 int last = input.lastIndexOf('}');
164 last = input.length();
166 if (first != -1 && last != -1 &&
168 result = input.substring(first+1, last);
171 return result.trim();
176 * Strips DECTalk commands from the input text. The DECTalk
177 * commands are anything inside "[" and "]".
179 public String stripDECTalkCommands(String content) {
180 int startPos = content.indexOf('[');
181 while (startPos != -1) {
182 int endPos = content.indexOf(']');
185 if (endPos == (content.length() - 1)) {
188 content = content.substring(endPos + 1);
191 if (endPos == (content.length() - 1)) {
192 content = content.substring(0, startPos);
194 String firstPart = content.substring(0, startPos);
195 String secondPart = content.substring(endPos + 1);
196 content = firstPart + " " + secondPart;
199 startPos = content.indexOf('[');
204 return content.trim();
209 * Speaks the given input text.
211 * @param input the input text to speak.
213 public abstract void speak(String input);
217 * Removes all the queued text.
219 public abstract void cancelAll();
223 * Sets the speaking rate.
225 * @param wpm the new speaking rate (words per minute)
227 public abstract void setRate(float wpm);
231 * Implements the run() method of Runnable
233 public synchronized void run() {
236 stripDECTalk = Utilities.getBoolean("stripDECTalk");
237 while (isSocketLive()) {
238 command = reader.readLine();
239 if (command != null) {
240 command = command.trim();
241 debugPrintln("IN : " + command);
243 int commandType = getCommandType(command);
245 if (commandType == EXIT_COMMAND) {
248 } else if (commandType == STOP_COMMAND) {
250 } else if (commandType == RATE_COMMAND) {
252 setRate(Float.parseFloat(
253 textInCurlyBrackets(command)));
254 } catch (NumberFormatException e) {
255 // ignore and do nothing
257 } else if (commandType != NOT_HANDLED_COMMAND) {
258 String content = textInCurlyBrackets(command);
260 content = stripDECTalkCommands(content);
262 if (content.length() > 0) {
265 // detect if emacspeak is trying to quit
266 detectQuitting(commandType, content);
268 debugPrintln("SPEAK:");
272 } catch (IOException ioe) {
273 ioe.printStackTrace();
275 debugPrintln("EmacspeakProtocolHandler: thread terminated");
281 * Returns true if the Socket is still alive.
283 * @return true if the Socket is still alive
285 private boolean isSocketLive() {
286 return (socket.isBound() &&
287 !socket.isClosed() && socket.isConnected() &&
288 !socket.isInputShutdown() && !socket.isOutputShutdown());
293 * Read a line of text. A line is considered to be terminated
294 * by any one of a line feed ('\n'), a carriage return ('\r'),
295 * or a carriage return followed immediately by a linefeed.
297 * @return A String containing the contents of the line,
298 * not including any line-termination
299 * characters, or null if the end of the stream has been reached
301 * @throws IOException if an I/O error occurs
303 private String readLine() throws IOException {
304 String command = null;
305 boolean repeat = false;
308 command = reader.readLine();
310 } catch (SocketTimeoutException ste) {
311 System.out.println("timed out");
316 repeat = isSocketLive();
325 * Detects and handles a possible emacspeak quitting sequence
326 * of commands, by looking at the given command type and content.
327 * If a quitting sequence is detected, it will close the socket.
328 * Note that this is not the best way to trap a quitting sequence,
329 * but I can't find another way to trap it. This method will
330 * do a socket.notifyAll() to tell objects waiting on the socket
331 * that it has been closed.
333 * @param commandType the command type
334 * @param content the contents of the command
336 private synchronized void detectQuitting(int commandType, String content)
338 if (commandType == QUEUE_COMMAND) {
339 lastQueuedCommand = content;
340 } else if (commandType == TTS_SAY_COMMAND) {
341 if (content.equals("no")) {
342 lastQueuedCommand = "";
343 } else if (content.equals("yes") &&
344 lastQueuedCommand.startsWith(stopQuestionStart)) {
353 * Prints the given message if the <code>debug</code> System property
354 * is set to <code>true</code>.
356 * @param message the message to print
358 public void debugPrintln(String message) {
360 System.out.println(message);