001    package toolbus.tool;
002    
003    import java.util.Iterator;
004    import java.util.LinkedList;
005    import java.util.List;
006    
007    import toolbus.IOperations;
008    import toolbus.TBTermFactory;
009    import toolbus.ToolBus;
010    import toolbus.ToolInstanceManager;
011    import toolbus.communication.IDataHandler;
012    import toolbus.communication.IIOHandler;
013    import toolbus.environment.Environment;
014    import toolbus.exceptions.ToolBusException;
015    import toolbus.logging.ILogger;
016    import toolbus.logging.IToolBusLoggerConstants;
017    import toolbus.logging.LoggerFactory;
018    import toolbus.tool.execution.IToolExecutor;
019    import toolbus.tool.execution.IToolExecutorFactory;
020    import toolbus.tool.execution.StreamHandler;
021    import aterm.AFun;
022    import aterm.ATerm;
023    import aterm.ATermAppl;
024    
025    /**
026     * Provides the interface to a tool instance.
027     * 
028     * @author Arnold Lankamp
029     */
030    public class ToolInstance implements IDataHandler, IOperations{
031            private final ToolDefinition toolDef;
032            private final ToolBus toolbus;
033            private final TBTermFactory tbfactory;
034            
035            private volatile IIOHandler ioHandler = null;
036            
037            private volatile StreamHandler streamHandler = null;
038            
039            private static int toolNr = 0;
040            
041            private final ATermAppl toolKey;
042            private final int toolID;
043            
044            private final List<ATerm> valuesFromTool;
045            private final List<ATerm> eventsFromTool;
046            private final List<ATerm> requestsFromTool;
047            
048            private final List<ATerm> performanceStats;
049            
050            private volatile ATerm lastDebugPerformanceStats = null;
051            
052            /**
053             * Constructor.
054             * 
055             * @param toolDef
056             *            The definition of the tool.
057             * @param toolbus
058             *            A reference to the toolbus associated with this tool instance.
059             */
060            public ToolInstance(ToolDefinition toolDef, ToolBus toolbus){
061                    super();
062                    
063                    this.toolDef = toolDef;
064                    this.toolbus = toolbus;
065                    this.tbfactory = toolbus.getTBTermFactory();
066                    
067                    synchronized(getClass()){
068                            this.toolID = toolNr++;
069                    }
070                    
071                    AFun afun = tbfactory.makeAFun(toolDef.getName(), 1, false);
072                    toolKey = tbfactory.makeAppl(afun, tbfactory.makeInt(toolID));
073                    
074                    valuesFromTool = new LinkedList<ATerm>();
075                    eventsFromTool = new LinkedList<ATerm>();
076                    requestsFromTool = new LinkedList<ATerm>();
077                    
078                    performanceStats = new LinkedList<ATerm>();
079            }
080            
081            /**
082             * Executes the tool that should be associated with this tool instance.
083             * 
084             * @throws ToolBusException
085             *            Thrown when the tool could not be executed.
086             */
087            public void executeTool() throws ToolBusException{
088                    IToolExecutorFactory toolExecutorFactory = toolbus.getToolExecutorFactory();
089                    
090                    IToolExecutor toolExecutor = toolExecutorFactory.getToolExecutor(this, toolDef, toolbus);
091                    toolExecutor.execute();
092            }
093            
094            /**
095             * Returns the key of the tool that is associated with this tool instance.
096             * 
097             * @return The key of the tool that is associated with this tool instance.
098             */
099            public ATermAppl getToolKey(){
100                    return toolKey;
101            }
102            
103            /**
104             * Returns the id of the tool that is associated with this tool instance.
105             * 
106             * @return The id of the tool that is associated with this tool instance.
107             */
108            public int getToolID(){
109                    return toolID;
110            }
111            
112            /**
113             * Returns the name of the tool that is associated with this tool instance.
114             * 
115             * @return The name of the tool that is associated with this tool instance.
116             */
117            public String getToolName(){
118                    return toolKey.getName();
119            }
120            
121            /**
122             * Sets the stream handler for this tool instance.
123             * 
124             * @param streamHandler
125             *            The stream handler to use.
126             */
127            public void setStreamHandler(StreamHandler streamHandler){
128                    this.streamHandler = streamHandler;
129            }
130            
131            /**
132             * @see toolbus.communication.IDataHandler#setIOHandler(IIOHandler)
133             */
134            public void setIOHandler(IIOHandler ioHandler){
135                    this.ioHandler = ioHandler;
136            }
137            
138            /**
139             * @see toolbus.communication.IDataHandler#receive(byte, ATerm)
140             */
141            public void receive(byte operation, ATerm aTerm){
142                    switch(operation){
143                            case EVENT:
144                                    synchronized(eventsFromTool){
145                                            eventsFromTool.add(aTerm);
146                                    }
147                                    break;
148                            case REQUEST:
149                                    synchronized(requestsFromTool){
150                                            requestsFromTool.add(aTerm);
151                                    }
152                                    break;
153                            case ACKDO:
154                                    goReady();
155                                    break;
156                            case VALUE:
157                                    goReady();
158                                    synchronized(valuesFromTool){
159                                            valuesFromTool.add(aTerm);
160                                    }
161                                    break;
162                            case DISCONNECT:
163                                    goDisconnected();
164                                    break;
165                            case CONNECT:
166                                    goConnected();
167                                    break;
168                            case PERFORMANCESTATS:
169                                    goReady();
170                                    performanceStats.add(aTerm);
171                                    break;
172                            case DEBUGPERFORMANCESTATS:
173                                    lastDebugPerformanceStats = aTerm;
174                                    break;
175                            default:
176                                    LoggerFactory.log("Message with unknown operation received from Tool: " + operation, ILogger.WARNING, IToolBusLoggerConstants.TOOLINSTANCE);
177                    }
178                    
179                    // Notify the ToolBus of the arrival of a message.
180                    toolbus.workArrived(this, operation);
181            }
182            
183            /**
184             * Sends an ack event to the with this tool instance associated tool.
185             * 
186             * @param aTerm
187             *            The ack event.
188             */
189            public void sendAckEvent(ATerm aTerm){
190                    send(ACKEVENT, aTerm);
191            }
192            
193            /**
194             * Sends an response to the with this tool instance associated tool.
195             * 
196             * @param aTerm
197             *            The response.
198             */
199            public void sendResponse(ATerm aTerm){
200                    send(RESPONSE, aTerm);
201            }
202            
203            /**
204             * Sends a do request to the with this tool instance associated tool.
205             * 
206             * @param aTerm
207             *            The do request.
208             */
209            public void sendDo(ATerm aTerm){
210                    send(DO, aTerm);
211            }
212            
213            /**
214             * Sends an evaluation request to the with this tool instance associated tool.
215             * 
216             * @param aTerm
217             *            The evaluation request.
218             */
219            public void sendEval(ATerm aTerm){
220                    send(EVAL, aTerm);
221            }
222            
223            /**
224             * Sends a termination request to the with this tool instance associated tool.
225             * 
226             * @param aTerm
227             *            The termination request.
228             */
229            public void sendTerminate(ATerm aTerm){
230                    send(TERMINATE, aTerm);
231            }
232            
233            /**
234             * Sends a performance statistics request to the with this tool instance associated tool.
235             * 
236             * @param aTerm
237             *            The performance statistics request.
238             */
239            public void sendPerformanceStatsRequest(ATerm aTerm){
240                    send(PERFORMANCESTATS, aTerm);
241            }
242            
243            /**
244             * Sends a debug performance statistics request to the with this tool instance associated tool.
245             */
246            public void sendDebugPerformanceStatsRequest(){
247                    send(DEBUGPERFORMANCESTATS, toolbus.getTBTermFactory().makeList());
248            }
249            
250            /**
251             * @see toolbus.communication.IDataHandler#send(byte, ATerm)
252             */
253            public void send(byte operation, ATerm aTerm){
254                    ioHandler.send(operation, aTerm);
255            }
256            
257            /**
258             * @see toolbus.communication.IDataHandler#terminate()
259             */
260            public void terminate(){
261                    ioHandler.terminate();
262            }
263            
264            /**
265             * @see toolbus.communication.IDataHandler#shutDown()
266             */
267            public void shutDown(){
268                    boolean connected = isConnected();
269                    
270                    goTerminated();
271                    
272                    ToolInstanceManager tim = toolbus.getToolInstanceManager();
273                    if(connected){ // If the tool is connected we only need to check in one collection.
274                            tim.remove(toolKey);
275                    }else{
276                            tim.remove(toolKey);
277                            tim.removePendingTool(toolKey);
278                            tim.removeDynamiclyConnectedTool(this);
279                    }
280                    
281                    toolbus.workArrived(this, IOperations.END);
282            }
283            
284            /**
285             * Checks if the tool that is associated with this tool instance was executed or connected on it's own initiative.
286             * 
287             * @return True if the tool was executed; false otherwise.
288             */
289            public boolean isExecutedTool(){
290                    return (streamHandler != null);
291            }
292            
293            /**
294             * Forcefully terminates the with this tool instance associated tool.
295             * NOTE: This only works for executed tools. Tools that connected on their own initiative won't
296             * be killed by the invocation of this method; however they will become unreachable and their
297             * state will be set to 'killed'.
298             */
299            public void kill(){
300                    boolean connected = isConnected();
301                    
302                    goKilled();
303                    
304                    ToolInstanceManager tim = toolbus.getToolInstanceManager();
305                    if(connected){ // If the tool is connected we only need to check in one collection.
306                            tim.remove(toolKey);
307                    }else{
308                            tim.remove(toolKey);
309                            tim.removePendingTool(toolKey);
310                            tim.removeDynamiclyConnectedTool(this);
311                    }
312                    
313                    if(streamHandler != null) streamHandler.destroy(); // Forcefully kills the tool.
314            }
315            
316            /**
317             * @see IDataHandler#exceptionOccured()
318             */
319            public void exceptionOccured(){
320                    goUnreachable();
321                    
322                    LoggerFactory.log("Tool crashed / disconnected: "+toolKey, ILogger.WARNING, IToolBusLoggerConstants.TOOLINSTANCE);
323            }
324            
325            /**
326             * Attempts to find a value that matches the given signature. If this is the case the
327             * environment will be updated.
328             * 
329             * @param pattern
330             *            The signature we need to match on.
331             * @param env
332             *            The enviroment in which we need to make updates, when a match have been made.
333             * @return Indicates if the matches was successful.
334             */
335            public boolean getValueFromTool(ATerm pattern, Environment env){
336                    synchronized(valuesFromTool){
337                            Iterator<ATerm> valuesIterator = valuesFromTool.iterator();
338                            while(valuesIterator.hasNext()){
339                                    boolean matches = tbfactory.matchPatternToValue(pattern, env, valuesIterator.next());
340                                    if(matches){
341                                            valuesIterator.remove();
342                                            return true;
343                                    }
344                            }
345                    }
346                    return false;
347            }
348            
349            /**
350             * Attempts to find a event that matches the given signature. If one is found the environment
351             * will be updated.
352             * 
353             * @param pattern
354             *            The signature we need to match on.
355             * @param env
356             *            The enviroment in which we need to make updates, when a match have been made.
357             * @return Indicates if the matches was successful.
358             */
359            public boolean getEventFromTool(ATerm pattern, Environment env){
360                    synchronized(eventsFromTool){
361                            Iterator<ATerm> eventsIterator = eventsFromTool.iterator();
362                            while(eventsIterator.hasNext()){
363                                    boolean matches = tbfactory.matchPatternToValue(pattern, env, eventsIterator.next());
364                                    if(matches){
365                                            eventsIterator.remove();
366                                            return true;
367                                    }
368                            }
369                    }
370                    return false;
371            }
372            
373            /**
374             * Attempts to find a request that matches the given signature. If one is found the environment
375             * will be updated.
376             * 
377             * @param pattern
378             *            The signature we need to match on.
379             * @param env
380             *            The enviroment in which we need to make updates, when a match have been made.
381             * @return Indicates if the matches was successful.
382             */
383            public boolean getRequestFromTool(ATerm pattern, Environment env){
384                    synchronized(requestsFromTool){
385                            Iterator<ATerm> requestsIterator = requestsFromTool.iterator();
386                            while(requestsIterator.hasNext()){
387                                    boolean matches = tbfactory.matchPatternToValue(pattern, env, requestsIterator.next());
388                                    if(matches){
389                                            requestsIterator.remove();
390                                            return true;
391                                    }
392                            }
393                    }
394                    return false;
395            }
396            
397            /**
398             * Attempts to find performance statistics that match the given signature. If one is found the
399             * environment will be updated.
400             * 
401             * @param aTerm
402             *            The signature we need to match on.
403             * @param env
404             *            The enviroment in which we need to make updates, when a match have been made.
405             * @return Indicates if the matches was successful.
406             */
407            public boolean getPerformanceStats(ATerm aTerm, Environment env){
408                    synchronized(performanceStats){
409                            Iterator<ATerm> performanceStatsIterator = performanceStats.iterator();
410                            while(performanceStatsIterator.hasNext()){
411                                    boolean matches = tbfactory.matchPatternToValue(aTerm, env, performanceStatsIterator.next());
412                                    if(matches){
413                                            performanceStatsIterator.remove();
414                                            return true;
415                                    }
416                            }
417                    }
418                    return false;
419            }
420            
421            /**
422             * Returns the last received batch of debug performance statistics.
423             * NOTE: This method will only be used in debug mode, by the debug ToolBus.
424             * 
425             * @return The last received batch of debug performance statistics.
426             */
427            public ATerm getLastDebugPerformanceStats(){
428                    return lastDebugPerformanceStats;
429            }
430            
431            private final static int UNCONNECTED = 0; // Start and end state.
432            private final static int READY = 2; // Ready to send (DO or EVAL).
433            private final static int BLOCKED = 3; // Waiting for response (ACKDO or VALUE).
434            private final static int DISCONNECTED = 4; // Termination requested by tool.
435            private final static int KILLED = 5; // User imposed error state.
436            private final static int UNREACHABLE = 6; // Error state.
437            private final static int TERMINATED = 7; // End state.
438            
439            private volatile int state = UNCONNECTED;
440            
441            private final Object stateLock = new Object();
442            
443            /**
444             * Notifies this tool instance that the tool connected.
445             */
446            private void goConnected(){
447                    state = READY;
448            }
449            
450            /**
451             * Notifies this tool instance that the tool is ready to receive a new DO or EVAL request.
452             */
453            public void goReady(){
454                    synchronized(stateLock){
455                            if(state == BLOCKED) state = READY; // Only go back to ready when we're still reachable (i.e. not killed/terminated/disconnected).
456                    }
457            }
458            
459            /**
460             * Notifies this tool instance that the tool has disconnected.
461             */
462            private void goDisconnected(){
463                    synchronized(stateLock){
464                            if(isConnected()) state = DISCONNECTED; // Only go back to disconnected when we're still reachable (i.e. not killed/terminated).
465                    }
466            }
467            
468            /**
469             * Notifies this tool instance that the tool has terminated.
470             */
471            private void goTerminated(){
472                    state = TERMINATED;
473            }
474            
475            /**
476             * Notifies this tool instance that the tool has been killed.
477             */
478            private void goKilled(){
479                    state = KILLED;
480            }
481            
482            /**
483             * Notifies this tool instance that the tool has become unreachable.
484             */
485            private void goUnreachable(){
486                    state = UNREACHABLE;
487            }
488            
489            /**
490             * Checks if the tool is connected.
491             * 
492             * @return True if the tool is connected; false otherwise.
493             */
494            public boolean isConnected(){
495                    return ((state & (READY | BLOCKED)) != 0); // We can do something smart here (0x2 indicates that we're connected since the ready and blocked states are the only two states for which this bit is set).
496            }
497            
498            /**
499             * Checks if the tool is ready.
500             * 
501             * @return True if the tool is ready; false otherwise.
502             */
503            public boolean isReady(){
504                    return (state == READY);
505            }
506            
507            /**
508             * Checks if the tool has disconnected.
509             * 
510             * @return True if the tool has disconnected; false otherwise.
511             */
512            public boolean isDisconnected(){
513                    return (state == DISCONNECTED);
514            }
515            
516            /**
517             * Checks if the tool has been terminated.
518             * 
519             * @return True if the tool has been terminated.
520             */
521            public boolean isTerminated(){
522                    return (state == TERMINATED);
523            }
524            
525            /**
526             * Checks if the tool has been killed.
527             * 
528             * @return True if the tool has been killed; false otherwise.
529             */
530            public boolean isKilled(){
531                    return (state == KILLED);
532            }
533            
534            /**
535             * Checks if the tool has become unreachable.
536             * 
537             * @return True if the tool has become unreachable.
538             */
539            public boolean isUnreachable(){
540                    return (state == UNREACHABLE);
541            }
542            
543            /**
544             * Notifies the tool instance that you want to send a DO or EVAL request to the tool and checks
545             * if this is possible.
546             * 
547             * @return True if it is possible to send a DO or EVAL request; false otherwise.
548             */
549            public boolean tryDoEval(){
550                    synchronized(stateLock){
551                            if(state == READY){
552                                    state = BLOCKED;
553                                    return true;
554                            }
555                    }
556                    return false;
557            }
558            
559            /**
560             * Returns an array of all queued rec-value terms for this tool instance.
561             * 
562             * @return An array of all queued rec-value terms for this tool instance.
563             */
564            public ATerm[] getQueuedValues(){
565                    synchronized(valuesFromTool){
566                            ATerm[] queuedValues = new ATerm[valuesFromTool.size()];
567                            valuesFromTool.toArray(queuedValues);
568                            return queuedValues;
569                    }
570            }
571            
572            /**
573             * Returns an array of all queued rec-event terms for this tool instance.
574             * 
575             * @return An array of all queued rec-event terms for this tool instance.
576             */
577            public ATerm[] getQueuedEvents(){
578                    synchronized(eventsFromTool){
579                            ATerm[] queuedEvents = new ATerm[eventsFromTool.size()];
580                            eventsFromTool.toArray(queuedEvents);
581                            return queuedEvents;
582                    }
583            }
584            
585            /**
586             * Returns an array of all queued rec-request terms for this tool instance.
587             * 
588             * @return An array of all queued rec-request terms for this tool instance.
589             */
590            public ATerm[] getQueuedRequests(){
591                    synchronized(requestsFromTool){
592                            ATerm[] queuedRequests = new ATerm[requestsFromTool.size()];
593                            requestsFromTool.toArray(queuedRequests);
594                            return queuedRequests;
595                    }
596            }
597            
598            /**
599             * Returns the status of this tool instance in string format.
600             * 
601             * @return The status of this tool instance in string format.
602             */
603            public String showStatus(){
604                    synchronized(stateLock){
605                            return "tool " + toolKey.toString() + "(state = " + state + ")";
606                    }
607            }
608    }