1// BEGIN LICENSE BLOCK 2// Version: CMPL 1.1 3// 4// The contents of this file are subject to the Cisco-style Mozilla Public 5// License Version 1.1 (the "License"); you may not use this file except 6// in compliance with the License. You may obtain a copy of the License 7// at www.eclipse-clp.org/license. 8// 9// Software distributed under the License is distributed on an "AS IS" 10// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 11// the License for the specific language governing rights and limitations 12// under the License. 13// 14// The Original Code is The ECLiPSe Constraint Logic Programming System. 15// The Initial Developer of the Original Code is Cisco Systems, Inc. 16// Portions created by the Initial Developer are 17// Copyright (C) 2006 Cisco Systems, Inc. All Rights Reserved. 18// 19// Contributor(s): Andrew Sadler, IC-Parc 20// 21// END LICENSE BLOCK 22 23package com.parctechnologies.eclipse; 24import java.io.*; 25import java.util.*; 26 27/** 28 * Wrapper class which implements the EclipseMultitaskConnection 29 * interface so as to make it seem as though the peer has control for 30 * the entire duration of ECLiPSe multitasking phases. 31 * 32 * <p> Objects of this class can only be constructed by calling the 33 * registerMultitask method of EclipseConnections. This class works by 34 * delegating most of the work to the EclipseConnection that created it. 35 * 36 * 37 * @see EclipseMultitaskConnection 38 * */ 39class EclipseMultitaskConnectionImpl implements EclipseMultitaskConnection { 40 static final String STATE_EXCEPTION_MESSAGE = "Attempt to confirm/terminate multitasking phase whilst not in multitasking phase."; 41 42 protected static final Atom peerConfirmMultitaskGoal = new Atom("peer_multitask_confirm"); 43 protected static final Atom peerTerminateMultitaskGoal = new Atom("peer_multitask_terminate"); 44 45 static final String START_MULTITASK_MESSAGE = "start_multitask"; 46 static final String END_MULTITASK_MESSAGE = "end_multitask"; 47 static final String INTERACT_MULTITASK_MESSAGE = "interact"; 48 49 static final int MULTITASK_RPC_TIMEOUT = 10000; 50 51 /** underlying eclipse connection */ 52 EclipseConnectionImpl eclipse; 53 54 /** queue on which multitask messages are recieved */ 55 FromEclipseQueue multitaskQueue; 56 57 /** Are we in a multitask phase, and if so which type. null=not in 58 multitask phase. */ 59 String multitaskPhase; 60 61 /** Holds the thread on which the multitask phase messages are begin 62 delivered. Any RPCs performed during a multitask phase fromthis 63 thread must be begin called from inside queue callbacks and so 64 they should NOT be delayed, but rather should be performed 65 immediately (so as to avoid deadlock). */ 66 Thread multitaskThread; 67 68 /** Holds the delayed RPCs */ 69 MultitaskGoalQueue multitaskGoalQueue; 70 71 /** Holds the MultitaskListeners */ 72 List multitaskListenerList ; 73 74 EclipseMultitaskConnectionImpl(EclipseConnectionImpl eclipse, 75 FromEclipseQueue multitaskQueue) throws IOException { 76 this.eclipse = eclipse; 77 this.multitaskQueue = multitaskQueue; 78 this.multitaskGoalQueue = new MultitaskGoalQueue(); 79 this.multitaskPhase = null; 80 this.multitaskListenerList = new LinkedList(); 81 this.multitaskThread = null; 82 // register the queue listener on the multitask queue 83 multitaskQueue.setListener(new MultitaskQL()); 84 } 85 86 public CompoundTerm rpc(String goal) throws EclipseException, IOException { 87 if ((multitaskPhase == null) || 88 (Thread.currentThread() == multitaskThread)) { 89 return eclipse.rpc(goal); 90 } 91 eclipse.testTerminated(); 92 return multitaskGoalQueue.execute(goal); 93 } 94 95 public CompoundTerm rpc(CompoundTerm goal) throws EclipseException, IOException { 96 if ((multitaskPhase == null) || 97 (Thread.currentThread() == multitaskThread)) { 98 return eclipse.rpc(goal); 99 } 100 eclipse.testTerminated(); 101 return multitaskGoalQueue.execute(goal); 102 } 103 104 public FromEclipseQueue getFromEclipseQueue(String name) throws EclipseException, IOException { 105 return eclipse.getFromEclipseQueue(name); 106 } 107 108 public ToEclipseQueue getToEclipseQueue(String name) throws EclipseException, IOException { 109 return eclipse.getToEclipseQueue(name); 110 } 111 112 public AsyncEclipseQueue getAsyncEclipseQueue(String name) throws EclipseException, IOException { 113 return eclipse.getAsyncEclipseQueue(name); 114 } 115 116 public void compile(File f) throws EclipseException, IOException { 117 rpc(new CompoundTermImpl("compile" , getPath(f))); 118 } 119 120 public String getPath(File f) throws EclipseException, IOException { 121 CompoundTerm call = new CompoundTermImpl("os_file_name" , null , f.getAbsolutePath() ); 122 return (String) rpc(call).arg(1); 123 } 124 125 public CompoundTerm rpc(String functor, Object arg1) throws EclipseException, IOException { 126 return rpc(new CompoundTermImpl(functor,arg1)); 127 } 128 129 public CompoundTerm rpc(String functor, Object arg1, 130 Object arg2) throws EclipseException, IOException { 131 return rpc(new CompoundTermImpl(functor, arg1, arg2)); 132 } 133 134 public CompoundTerm rpc(String functor, Object arg1, 135 Object arg2, Object arg3) throws EclipseException, IOException { 136 return rpc(new CompoundTermImpl(functor, arg1, arg2, arg3)); 137 } 138 139 public CompoundTerm rpc(String functor, Object arg1, 140 Object arg2, Object arg3, Object arg4) throws EclipseException, IOException { 141 return rpc(new CompoundTermImpl(functor, arg1, arg2, arg3, arg4)); 142 } 143 144 public CompoundTerm rpc(String functor, Object arg1, 145 Object arg2, Object arg3, Object arg4, 146 Object arg5) throws EclipseException, IOException { 147 return rpc(new CompoundTermImpl(functor, arg1, arg2, arg3, arg4, arg5)); 148 } 149 150 public CompoundTerm rpc(String functor, Object[] args) throws EclipseException, IOException { 151 return rpc(new CompoundTermImpl(functor, args)); 152 } 153 154 public CompoundTerm rpc(Object[] goalTerm) throws EclipseException, IOException { 155 return rpc(new CompoundTermImpl(goalTerm)); 156 } 157 158 public Atom getPeerName() { 159 return eclipse.getPeerName(); 160 } 161 162 /** 163 * Add the given listener to this EclipseMultitaskConnection. 164 * @return <code>this</code>. Since this object is already 165 * registered for multitask phases. */ 166 public EclipseMultitaskConnection registerMultitask(MultitaskListener multitaskListener) throws EclipseException,IOException { 167 eclipse.testTerminated(); 168 if (multitaskListener != null) { 169 multitaskListenerList.add(multitaskListener); 170 } 171 return this; 172 } 173 174 /** 175 * Set the property that a multitasking phase is in progress 176 */ 177 void setMultitaskPhase(String type) { 178 multitaskPhase = type ; 179 } 180 181 /** 182 * Perform any pending RPC's 183 */ 184 void processMultitaskTimeSlice() { 185 // execute any delayed RPC's using the underlying EclipseConnection 186 multitaskGoalQueue.process(eclipse); 187 } 188 189 190 private static class MultitaskGoalQueue { 191 /** Stores the MultitaskGoals yet to be processed by the 192 multitask timeslice handler */ 193 private List pendingGoals = new LinkedList(); 194 195 /** Stores any exception which occurs as a result of errors in the 196 multitask protocol */ 197 Exception multitaskProtocolException; 198 199 synchronized void setProtocolException(Exception ioe) { 200 if (multitaskProtocolException != null) { 201 multitaskProtocolException = ioe; 202 } 203 } 204 205 synchronized void testProtocolException() throws EclipseException,IOException { 206 if (multitaskProtocolException != null) { 207 if (multitaskProtocolException instanceof EclipseException) { 208 throw (EclipseException)multitaskProtocolException; 209 } 210 if (multitaskProtocolException instanceof IOException) { 211 throw (IOException)multitaskProtocolException; 212 } 213 throw new IOException("Multitask protocol error:"+ 214 multitaskProtocolException.getMessage()); 215 } 216 } 217 218 /** Puts the Object (String or CompoundTerm) on the list of goals 219 to be executed and then wait until it has been. */ 220 public CompoundTerm execute(Object goal) throws IOException, EclipseException { 221 MultitaskGoal mg = new MultitaskGoal(goal); 222 synchronized(this) { 223 pendingGoals.add(mg); 224 } 225 synchronized(mg) { 226 while((mg.result == null) && (mg.exception == null)) { 227 try { 228 mg.wait(MULTITASK_RPC_TIMEOUT); 229 } 230 catch(InterruptedException ie){} 231 testProtocolException(); 232 } 233 } 234 // if an exception occured during the RPC, then re-throw it in 235 // the caller thread 236 if (mg.exception != null) { 237 if (mg.exception instanceof IOException) { 238 throw (IOException)(mg.exception).fillInStackTrace(); 239 } else if (mg.exception instanceof EclipseException) { 240 throw (EclipseException)(mg.exception).fillInStackTrace(); 241 } else { 242 throw new EclipseException(mg.exception.getMessage()); 243 } 244 } 245 return mg.result; 246 } 247 248 /** Process the pending goals. This should only be called from 249 within the handler for the multitask message queue. */ 250 public void process(EclipseConnection eclipse) { 251 Iterator it; 252 synchronized(this) { 253 if (pendingGoals.isEmpty()) { 254 return; 255 } 256 // get the iterator for the current pending goals 257 it = pendingGoals.iterator(); 258 // clear the pending goals before performing any RPC to avoid 259 // infinite recusion 260 pendingGoals = new LinkedList(); 261 } 262 while(it.hasNext()) { 263 MultitaskGoal mg = (MultitaskGoal)(it.next()); 264 try { 265 if (mg.goal instanceof CompoundTerm) { 266 mg.result = eclipse.rpc((CompoundTerm)mg.goal); 267 } else if (mg.goal instanceof String) { 268 mg.result = eclipse.rpc((String)mg.goal); 269 } else { 270 throw new EclipseException("Unknown object type ("+mg.goal+") for rpc."); 271 } 272 } catch(IOException ioe) { 273 mg.exception = ioe; 274 } catch(EclipseException ee) { 275 mg.exception = ee; 276 } 277 synchronized(mg) { 278 /* wake up the threads which were waiting for the results */ 279 mg.notifyAll(); 280 } 281 } 282 } 283 } 284 285 /** Class to hold the goal to be executed by the Multitask.timeslice 286 * thread */ 287 private static class MultitaskGoal 288 { 289 private Object goal; 290 private CompoundTerm result; 291 private Exception exception; 292 293 private MultitaskGoal(Object goal) 294 { 295 this.goal = goal; 296 } 297 } 298 299 300 301 /** Queue listener attached to the "Multitask" stream. Behaviour is 302 to set the state to indicate being in multi-tasking 303 mode */ 304 private class MultitaskQL implements QueueListener 305 { 306 protected CompoundTerm eventTerm; 307 EXDRInputStream eis = null; 308 309 310 public void dataAvailable(Object source) 311 { 312 if(eis == null) { 313 FromEclipseQueue feq = (FromEclipseQueue) source; 314 eis = new EXDRInputStream(feq); 315 } 316 try { 317 eventTerm = (CompoundTerm) eis.readTerm(); 318 processEvent(); 319 } catch(EclipseException ee) { 320 // record the exception so it can be thrown by the next thread 321 // to attempt multitasking RPC 322 multitaskGoalQueue.setProtocolException(ee); 323 } catch(IOException ioe) { 324 // record the exception so it can be thrown by the next thread 325 // to attempt multitasking RPC 326 multitaskGoalQueue.setProtocolException(ioe); 327 } 328 } 329 330 void processEvent() throws EclipseException,IOException { 331 String functor = eventTerm.functor(); 332 if (START_MULTITASK_MESSAGE.equals(functor)) { 333 Object typeTerm = eventTerm.arg(1); 334 if (typeTerm instanceof String) { 335 setMultitaskPhase((String)typeTerm); 336 } else if (typeTerm instanceof CompoundTerm) { 337 setMultitaskPhase(((CompoundTerm)typeTerm).functor()); 338 } else { 339 throw new IOException("Multitask protocol error. Unexpected multitasking phase type:"+typeTerm); 340 } 341 /* Record the thread on which this messages was delivered */ 342 multitaskThread = Thread.currentThread(); 343 /* notify all listeners */ 344 for(Iterator it = multitaskListenerList.iterator(); it.hasNext(); ) { 345 MultitaskListener ml = (MultitaskListener)it.next(); 346 ml.starting(EclipseMultitaskConnectionImpl.this,getMultitaskPhase()); 347 } 348 /* perform any pending rpc*/ 349 processMultitaskTimeSlice(); 350 } else if (END_MULTITASK_MESSAGE.equals(functor)) { 351 /* notify all listeners */ 352 for(Iterator it = multitaskListenerList.iterator(); it.hasNext(); ) { 353 MultitaskListener ml = (MultitaskListener)it.next(); 354 ml.ending(EclipseMultitaskConnectionImpl.this,getMultitaskPhase()); 355 } 356 /* perform any pending rpc*/ 357 processMultitaskTimeSlice(); 358 /* Clear the thread on which this messages was delivered */ 359 multitaskThread = null; 360 /* leave the multitask phase */ 361 setMultitaskPhase(null); 362 } else if (INTERACT_MULTITASK_MESSAGE.equals(functor)) { 363 /* perform any pending rpc*/ 364 processMultitaskTimeSlice(); 365 } 366 } 367 368 public void dataRequest(Object source){} 369 } 370 371 /** 372 * Gets the non-"multitasking aware" EclipseConnection from which 373 * this "multitasking aware" connection was created.. 374 * 375 * <p>This method is provided incase the caller needs access to the 376 * type of the underlying connection, or if the caller requires the 377 * blocking RPC semantics of the underlying connection. */ 378 public EclipseConnection getEclipseConnection() { 379 return eclipse; 380 } 381 382 protected String getMultitaskPhase() throws EclipseException, IOException { 383 eclipse.testTerminated(); 384 multitaskGoalQueue.testProtocolException(); 385 return multitaskPhase; 386 } 387 388 public void multitaskConfirm() throws EclipseException, IOException, IllegalStateException { 389 synchronized(this) { 390 if (multitaskPhase == null) { 391 throw new IllegalStateException(STATE_EXCEPTION_MESSAGE); 392 } 393 } 394 rpc(peerConfirmMultitaskGoal); 395 } 396 397 public void multitaskTerminate() throws EclipseException, IOException, IllegalStateException { 398 synchronized(this) { 399 if (multitaskPhase == null) { 400 throw new IllegalStateException(STATE_EXCEPTION_MESSAGE); 401 } 402 } 403 rpc(peerTerminateMultitaskGoal); 404 } 405} 406