001    package toolbus.adapter.wish;
002    
003    import java.io.BufferedReader;
004    import java.io.IOException;
005    import java.io.InputStream;
006    import java.io.InputStreamReader;
007    import java.io.OutputStream;
008    import java.net.InetAddress;
009    import java.util.ArrayList;
010    import java.util.List;
011    
012    import toolbus.adapter.AbstractTool;
013    import aterm.AFun;
014    import aterm.ATerm;
015    import aterm.ATermAppl;
016    import aterm.ATermList;
017    import aterm.pure.PureFactory;
018    
019    public class WishAdapter extends AbstractTool{
020            private final static String WISH_COMMAND = "wish";
021    
022            private String scriptName = null;
023            private String tbtcl = null;
024            private String libdir = null;
025            private final List<String> arguments;
026    
027            private Process process = null;
028            private OutputStream wishInputStream = null;
029            private OutputStreamHandler outputStreamHandler = null;
030            private ErrorStreamHandler errorStreamHandler = null;
031    
032            private final byte[] spaceBytes;
033            private final byte[] startCallBytes;
034            private final byte[] endCallBytes;
035            private final byte[] startAckEventBytes;
036            private final byte[] endAckEventBytes;
037            private final byte[] startTerminateBytes;
038            private final byte[] endTerminateBytes;
039    
040            private final Object valueLock = new Object();
041            private ATerm value = null;
042    
043            public WishAdapter(){
044                    super();
045    
046                    arguments = new ArrayList<String>();
047    
048                    spaceBytes = " ".getBytes();
049                    startCallBytes = "if [catch {".getBytes();
050                    endCallBytes = "} msg] {TBerror $msg}\n".getBytes();
051                    startAckEventBytes = "rec-ack-event {".getBytes();
052                    endAckEventBytes = "}".getBytes();
053                    startTerminateBytes = "rec-terminate {".getBytes();
054                    endTerminateBytes = "}".getBytes();
055            }
056    
057            public void connect(String[] args) throws Exception{
058                    String toolName = null;
059                    int toolID = -1;
060    
061                    InetAddress host = null;
062                    int port = -1;
063    
064                    arguments.clear(); // Insurance, this isn't strictly needed.
065    
066                    for(int i = 0; i < args.length; i++){
067                            String arg = args[i];
068                            if(arg.equals("-TB_TOOL_NAME")){
069                                    toolName = args[++i];
070                            }else if(arg.equals("-TB_TOOL_ID")){
071                                    toolID = Integer.parseInt(args[++i]);
072                            }else if(arg.equals("-TB_HOST")){
073                                    host = InetAddress.getByName(args[++i]);
074                            }else if(arg.equals("-TB_PORT")){
075                                    port = Integer.parseInt(args[++i]);
076                            }else if(arg.equals("-script")){
077                                    scriptName = args[++i];
078                            }else if(arg.equals("-tbtcl")){
079                                    tbtcl = args[++i];
080                            }else if(arg.equals("-libdir")){
081                                    libdir = args[++i];
082                            }else if(arg.equals("-script-args")){
083                                    while(++i < args.length){
084                                            arguments.add(args[i]);
085                                    }
086                            }else{
087                                    throw new RuntimeException("Unknown argument: " + arg);
088                            }
089                    }
090    
091                    if(toolName == null) throw new RuntimeException("Missing tool identification.");
092                    if(scriptName == null) throw new RuntimeException("No script name supplied.");
093                    if(tbtcl == null || libdir == null) throw new RuntimeException("No library paths supplied.");
094    
095                    toolBridge = new WishAdapterBridge(getFactory(), this, toolName, toolID, host, port);
096    
097                    executeScript();
098    
099                    toolBridge.run();
100    
101                    startHandlingIO();
102    
103                    int exitCode = process.waitFor();
104                    if(exitCode != 0){
105                            System.err.println("Script exited with error code: " + exitCode);
106                            System.exit(exitCode);
107                    }
108            }
109    
110            private void executeScript() throws IOException{
111                    String[] command = new String[3];
112                    command[0] = WISH_COMMAND;
113                    command[1] = "-name";
114                    command[2] = getToolBridge().getToolName();
115    
116                    ProcessBuilder pb = new ProcessBuilder(command);
117    
118                    process = pb.start();
119    
120                    wishInputStream = process.getOutputStream();
121                    outputStreamHandler = new OutputStreamHandler(process.getInputStream());
122                    errorStreamHandler = new ErrorStreamHandler(process.getErrorStream());
123    
124                    initWish();
125            }
126    
127            private void initWish() throws IOException{
128                    wishInputStream.write("source ".getBytes());
129                    wishInputStream.write(tbtcl.getBytes());
130                    wishInputStream.write("\n".getBytes());
131    
132                    wishInputStream.write("set argv { ".getBytes());
133                    int nrOfArguments = arguments.size();
134                    int i = 0;
135                    while(i < nrOfArguments){
136                            wishInputStream.write(arguments.get(i++).getBytes());
137                            wishInputStream.write(spaceBytes);
138                    }
139                    wishInputStream.write("}\n".getBytes());
140    
141                    wishInputStream.write("set argc ".getBytes());
142                    wishInputStream.write(("" + nrOfArguments).getBytes());
143                    wishInputStream.write("\n".getBytes());
144    
145                    wishInputStream.write("set TB_LIBDIR ".getBytes());
146                    wishInputStream.write(libdir.getBytes());
147                    wishInputStream.write("\n".getBytes());
148    
149                    wishInputStream.write("source ".getBytes());
150                    wishInputStream.write(scriptName.getBytes());
151                    wishInputStream.write("\n".getBytes());
152    
153                    wishInputStream.flush();
154            }
155    
156            private void startHandlingIO(){
157                    Thread outputStreamHandlerThread = new Thread(outputStreamHandler);
158                    outputStreamHandlerThread.setName("Output stream handler");
159                    outputStreamHandlerThread.setDaemon(true);
160                    outputStreamHandlerThread.start();
161    
162                    Thread errorStreamHandlerThread = new Thread(errorStreamHandler);
163                    errorStreamHandlerThread.setName("Error stream handler");
164                    errorStreamHandlerThread.setDaemon(true);
165                    errorStreamHandlerThread.start();
166            }
167    
168            protected void valueReady(ATerm v){
169                    value = v;
170                    synchronized(valueLock){
171                            valueLock.notify();
172                    }
173            }
174    
175            private void dumpArgument(ATerm arg) throws IOException{
176                    if(arg.getType() == ATerm.LIST){
177                            dumpListArgument((ATermList) arg);
178                    }else{
179                            wishInputStream.write(arg.toString().getBytes());
180                    }
181            }
182    
183            private void dumpListArgument(ATermList list) throws IOException{
184                    if(list.isEmpty()){
185                            wishInputStream.write("{}".getBytes());
186                    }else{
187                            wishInputStream.write("{".getBytes());
188                            while(!list.isEmpty()){
189                                    ATerm head = list.getFirst();
190                                    dumpArgument(head);
191                                    list = list.getNext();
192                                    if(!list.isEmpty()){
193                                            wishInputStream.write(spaceBytes);
194                                    }
195                            }
196                            wishInputStream.write("}".getBytes());
197                    }
198            }
199    
200            private void dumpReceivedTerm(ATerm aTerm){
201                    ATermAppl doTerm = (ATermAppl) aTerm;
202                    AFun fun = doTerm.getAFun();
203                    String functionName = fun.getName();
204                    ATerm[] arguments = doTerm.getArgumentArray();
205                    int nrOfArguments = arguments.length;
206    
207                    try{
208                            wishInputStream.write(startCallBytes);
209                            wishInputStream.write(functionName.getBytes());
210                            wishInputStream.write(spaceBytes);
211                            int i = 0;
212                            while(i < nrOfArguments){
213                                    dumpArgument(arguments[i++]);
214                                    wishInputStream.write(spaceBytes);
215                            }
216                            wishInputStream.write(endCallBytes);
217    
218                            wishInputStream.flush();
219                    }catch(IOException ioex){
220                            ioex.printStackTrace();
221                            System.err.println("Something went terribly wrong with the TCL/TK tool. Committing suicide now ....");
222                            System.exit(0); // Kill yourself.
223                    }
224            }
225    
226            public void receiveDo(ATerm aTerm){
227                    dumpReceivedTerm(aTerm);
228            }
229    
230            public ATerm receiveEval(ATerm aTerm){
231                    dumpReceivedTerm(aTerm);
232                    // Receive the value.
233                    synchronized (valueLock){
234                            while(value == null){
235                                    try{
236                                            valueLock.wait();
237                                    }catch(InterruptedException irex){
238                                            // Ignore this exception.
239                                    }
240                            }
241                    }
242    
243                    ATerm result = value;
244                    value = null; // Reset the value field.
245    
246                    return result;
247            }
248    
249            public void receiveTerminate(ATerm aTerm){
250                    try{
251                            wishInputStream.write(startCallBytes);
252                            wishInputStream.write(startTerminateBytes);
253                            wishInputStream.write(spaceBytes);
254    
255                            wishInputStream.write(aTerm.toString().getBytes());
256                            wishInputStream.write(spaceBytes);
257    
258                            wishInputStream.write(endTerminateBytes);
259                            wishInputStream.write(endCallBytes);
260    
261                            wishInputStream.flush();
262                    }catch(IOException ioex){
263                            ioex.printStackTrace();
264                            System.err.println("Something went terribly wrong with the TCL/TK tool. Committing suicide now ....");
265                            System.exit(0); // Kill yourself.
266                    }
267            }
268    
269            public void receiveAckEvent(ATerm aTerm){
270                    ATermList ackCallbackData = (ATermList) aTerm;
271    
272                    try{
273                            wishInputStream.write(startCallBytes);
274                            wishInputStream.write(startAckEventBytes);
275                            wishInputStream.write(spaceBytes);
276    
277                            ATermList empty = getFactory().makeList();
278                            while(ackCallbackData != empty){
279                                    ATermList next = ackCallbackData.getNext();
280                                    ATerm first = ackCallbackData.getFirst();
281    
282                                    wishInputStream.write(first.toString().getBytes());
283                                    wishInputStream.write(spaceBytes);
284    
285                                    ackCallbackData = next;
286                            }
287                            wishInputStream.write(endAckEventBytes);
288                            wishInputStream.write(endCallBytes);
289    
290                            wishInputStream.flush();
291                    }catch(IOException ioex){
292                            ioex.printStackTrace();
293                            System.err.println("Something went terribly wrong with the TCL/TK tool. Committing suicide now ....");
294                            System.exit(0); // Kill yourself.
295                    }
296            }
297    
298            private class OutputStreamHandler implements Runnable{
299                    private final InputStream inputStream;
300    
301                    public OutputStreamHandler(InputStream inputStream){
302                            this.inputStream = inputStream;
303                    }
304    
305                    public void run(){
306                            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
307                            PureFactory factory = getFactory();
308    
309                            String line = null;
310                            try{
311                                    while((line = reader.readLine()) != null){
312                                            ATermAppl term = (ATermAppl) factory.parse(line);
313                                            AFun fun = term.getAFun();
314                                            String operation = fun.getName();
315    
316                                            // The operation has already been interned, so match
317                                            // pointers instead of using equals.
318                                            if(operation == "snd-event"){
319                                                    ATerm sndEventTerm = term.getArgument(0);
320                                                    sendEvent(sndEventTerm);
321                                            }else if(operation == "snd-value"){
322                                                    ATerm valueTerm = term.getArgument(0);
323                                                    valueReady(valueTerm);
324                                            }else if(operation == "snd-disconnect"){
325                                                    disconnect(factory.makeList());
326                                            }
327                                    }
328                            }catch(IOException ioex){
329                                    ioex.printStackTrace();
330                            }catch(RuntimeException rex){
331                                    rex.printStackTrace();
332                            }finally{
333                                    // Close the reader.
334                                    try{
335                                            reader.close();
336                                    }catch(IOException ioex){
337                                            ioex.printStackTrace();
338                                    }
339                            }
340                    }
341            }
342    
343            private class ErrorStreamHandler implements Runnable{
344                    private final InputStream inputStream;
345    
346                    public ErrorStreamHandler(InputStream inputStream){
347                            this.inputStream = inputStream;
348                    }
349    
350                    public void run(){
351                            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
352    
353                            String line = null;
354                            try{
355                                    while((line = reader.readLine()) != null){
356                                            System.err.println(line);
357                                    }
358                            }catch(IOException ioex){
359                                    ioex.printStackTrace();
360                            }catch(RuntimeException rex){
361                                    rex.printStackTrace();
362                            }finally{
363                                    // Close the reader.
364                                    try{
365                                            reader.close();
366                                    }catch(IOException ioex){
367                                            ioex.printStackTrace();
368                                    }
369                            }
370                    }
371            }
372    
373            public static void main(String[] args) throws Exception{
374                    WishAdapter wishAdapter = new WishAdapter();
375                    wishAdapter.connect(args);
376            }
377    }