Source code: edu/mit/media/hive/agent/AgentImpl.java
1 // $Id: AgentImpl.java,v 2.1 1999/11/13 06:46:00 raffik Exp $
2 // Hive. Copyright (c) 1998-1999, The Massachusetts Institute of Technology.
3 // All rights reserved. Distributed with no warranty, without even the
4 // implied warranty of merchantability or fitness for a particular purpose.
5 // For more details see COPYING.GPL, the GNU General Public License.
6
7 package edu.mit.media.hive.agent;
8 import java.rmi.RemoteException;
9 import java.rmi.server.UnicastRemoteObject;
10 import java.io.Serializable;
11 import java.io.ObjectInputStream;
12 import java.io.ObjectOutputStream;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.util.Vector;
16 import java.util.Hashtable;
17 import java.awt.event.ActionEvent;
18
19 import edu.mit.media.hive.Global;
20 import edu.mit.media.hive.rdf.Description;
21 import edu.mit.media.hive.rdf.DescSet;
22 import edu.mit.media.hive.rdf.Lookup;
23 import edu.mit.media.hive.cell.RemoteCell;
24 import edu.mit.media.hive.cell.Cell;
25 import edu.mit.media.hive.cell.AgentDeliveryException;
26 import edu.mit.media.hive.shadow.cell.ComponentManagerShadow;
27 import edu.mit.media.hive.shadow.ShadowNotFoundException;
28 import edu.mit.media.hive.support.Debug;
29 import edu.mit.media.hive.support.CellAddress;
30 import edu.mit.media.hive.support.IconLoader;
31 import edu.mit.media.hive.support.PPM;
32 import edu.mit.media.hive.support.SerializableImage;
33
34 import org.w3c.dom.Document;
35
36 /** Base class for Hive agent implementations.
37 *
38 * An agent has a life cycle.<ol>
39 * <li>Once in its lifetime, the agent is constructed by the no argument
40 * constructor.
41 * <li>The agent arrives on a server; its <code>doLocalSetup()</code>
42 * method is called. This happens whenever it first comes to a server,
43 * whether created by <code>Cell.createNewAgent(Class)</code> or
44 * when it moves to the server.
45 * <li>The agent's <code>doBehavior()</code> method is called. This method
46 * is the agent's main loop, where it's behavior is implemented. The
47 * agent is free to do what it wants, but should respect
48 * <code>timeToStop</code>. The agent sould never exit this method,
49 * a convienience method <code>waitUntilDeath</code> is provided for this
50 * purpose.
51 * <li>When an agent is being killed, the notification <code>onDying</code>
52 * method is called. The <code>onMoving</code> is called for the
53 * notification for moving
54 * <li><code>onLocalCleanup</code> is called to ask the agent to
55 * free all its local resources. The reason for agent death can
56 * be discovered by looking at the value of <code>stopCode</code> if
57 * cleanup is dependant on the "type" of agent death
58 * </ol>
59
60 * An agent might travel to another server, either by moving itself
61 * or being moved by someone authorized. The <code>moveTo()</code>
62 * method initiates the move: when the agent arrives at the remote
63 * server, <code>arriveAt()</code> and <code>doBehaviour()</code>
64 * will be called again.
65 *
66 * All agents have a notion of a list of "connections", which agents
67 * they talk to. It is up to the agent to define the semantics of
68 * what a connect means. Agents also have an icon that represents
69 * them: by default this icon is loaded from
70 * icons/AgentClassName.ppm.
71 *
72 * @author Nelson Minar <nelson@media.mit.edu>
73 * @version $Revision: 2.1 $
74 , */
75 public abstract class AgentImpl
76 extends UnicastRemoteObject
77 implements Agent, Serializable {
78
79 /** Pointer to the server I am currently on.
80 */
81 protected transient Cell myCell = null;
82
83 /**
84 * Pointer to the thread group for this agent -- ell behaved
85 * agents, if they need to spawn separate threads, will make those
86 * threads members of this agents thread group. There is no way,
87 * unfortunately, for Hive to enforce this though
88 */
89 protected transient ThreadGroup agentThreadGroup = null;
90
91 /** Pointer to my RDF description.
92 */
93 protected Description description;
94
95
96 /**
97 * Just describing whether or not this agent is ready for
98 * operation or not
99 */
100 protected transient boolean readyFlag = false;
101
102 /** Variable indicating this agent should die soon. This is set
103 * asynchronously, typically by the server. Agents should inspect
104 * this in their main loop.
105 *
106 * @see doBehavior
107 */
108 protected transient boolean timeToStop = false;
109
110 /**
111 * Variable describing the reason why an agent was asked to stop
112 */
113 protected transient int stopCode;
114
115 /**
116 * The different possible stop codes for the agents
117 */
118 public static final int AGENTKILLED = 1;
119 public static final int AGENTMOVED = 2;
120
121
122 /** The icon that represents this agent. Should be loaded at create time.
123 */
124 protected SerializableImage icon = null;
125 protected String iconName = "";
126
127 /** Hashtable and list for the popup menu items or their non-graphical
128 ** equivalents.
129 **/
130 protected Hashtable commands = new Hashtable(); /** Hashtable of CommandObjects */
131 protected Vector commandList = new Vector(); /** List of the CommandObject's names */
132
133 /** Basic constructor for agents. This only gets called once in an
134 * agent's lifetime, when it is first created. It is <b>not</b> called
135 * when the agent arrives at a new host.
136 *
137 * @see doLocalSetup Note - because of Java syntax rules, all subclasses
138 * must explicitly declare a constructor, even if all it does is call
139 * the superclass constructor.
140 */
141 public AgentImpl() throws RemoteException {
142 super();
143 loadIcon();
144 }
145
146 /** Return the semantic description of this agent.
147 */
148 public Description getDescription() {
149 return description;
150 }
151
152 /** Set the semantic deescription of this agent.
153 */
154 public void setDescription( Description desc ) { this.description = desc; }
155
156
157 /**
158 * Get the value of iconName.
159 * @return Value of iconName.
160 */
161 public String getIconName() { return iconName; }
162
163 /**
164 * Set the value of iconName.
165 * @param v Value to assign to iconName.
166 */
167 public void setIconName( String v ) {
168 this.iconName = v;
169 loadIcon();
170 }
171
172 public String getName() {
173 String nn = Lookup.getNickname(this);
174 if(nn == null)
175 return Global.shortClassName(this);
176 else
177 return nn;
178 }
179
180 /** Tell this agent to connect to some other agent.
181 * It is up to the agent to define the semantics of what this means.
182 * By default, AgentImpl just prints an error message and returns false.
183 *
184 * @param otherAgent reference to the agent to connect to
185 * @return whether the connection was successful.
186 */
187 public boolean connectTo( Agent otherAgent ) {
188 Debug.notice("There is no default connection for an agent.");
189 return false;
190 }
191
192 /** Disconnects this agent from all agents.
193 */
194 public void disconnectFromAll() {
195 Debug.notice( "There is no default disconnection for an agent." );
196 }
197
198 /** Disconnects this agent from the specified other agent.
199 */
200 public void disconnectFrom( Agent otherAgent ) {
201 Debug.notice( "There is no default disconnection for an agent." );
202 }
203
204 /**
205 * Handle the setup when an agent arrives on a host -- it is
206 * appropriate to allocate system resources (file descriptors,
207 * etc) here and even to locate other agents that this agent needs
208 * to communicate with.
209 *
210 * This method replaces arriveAt
211 *
212 * @param s the server we are now on
213 */
214 public void doLocalSetup() { }
215
216 /**
217 * Set the server flag on the agent. Normally user code should not call
218 * this - creation code in the server should handle this
219 */
220 public void setCell( Cell s ) {
221 // This shouldn't happen, but it's not exactly an error.
222 if( myCell != null ) {
223 Debug.warning( "Warning! setCell() was called on " + Global.shortString( this ) + " when it had a server set!" );
224 }
225 myCell = s;
226 }
227
228 /**
229 * Set the thread group of this agent. Normally user code should
230 * not call this -- the creation methods in the server will set
231 * this properly
232 */
233 public void setThreadGroup( ThreadGroup tg ) {
234 this.agentThreadGroup = tg;
235 }
236
237 /**
238 * Do the basic behavior for an agent, the agent's main loop.
239 * Override this to provide your particular agent's behavior.
240 * <B>Note</b>: the doBehavior loop should not exit. Please call
241 * waitUntilDeath() at the end of this method, this will prevent
242 * the Cell from believing this agent prematurely exited.
243 *
244 * @see timeToStop
245 * @see doLocalSetup */
246 public abstract void doBehavior();
247
248 /** This is a silly hack to prevent any of us from repeating the
249 * silliest and most aggravating error of all time. In America,
250 * behavior has no 'u'
251 *
252 * @author Oliver Roup (oroup@mit.edu)
253 * @deprecated
254 */
255 public void doBehaviour() {
256 Debug.println( Debug.ERROR, "You misspelled behavior, and that is the source of your troubles..." );
257 }
258
259 /**
260 * Used at the end of the doBehavior method. Simply wait until
261 * the timeToStop flag has been set and the notify method has been
262 * called so we can exit properly,
263 *
264 * @see doBehavior
265 */
266 protected void waitUntilDeath() {
267 while( !timeToStop )
268 synchronized( this ) {
269 try {
270 wait();
271 } catch( InterruptedException error ) { }
272 }
273 }
274
275 /**
276 * Used to notify the agent that it is about to be moved
277 */
278 public void onMoving() { }
279
280 /**
281 * Move this agent to a new host.
282 *
283 * @param address
284 */
285 public void moveTo( CellAddress address ) throws AgentDeliveryException {
286 Debug.notice( Global.shortString( this ) + " moveTo(" + address + ")." );
287 myCell.moveAgent( this, address );
288 }
289
290 /**
291 * Command the agent to free its local resources here. It may or
292 * may not equal what happens when the agent is supposed to die --
293 * but here would be the appropriate place to free File
294 * descriptors, and stop threads, but do not do anything related
295 * to the agent community -- do not notify them that this agent is
296 * about to die. When this method terminates, this agent should
297 * be ready to be killed
298 */
299 public void doLocalCleanup() { }
300
301 /**
302 * Used to notify the agent that it is about to be killed
303 */
304 public void onDying() { }
305
306 /**
307 * Ask the agent to politely die -- by default, the agent just
308 * relays the call to the server and asks it tkill itself
309 */
310 public void diePlease() {
311 myCell.killAgent( (Agent)this );
312
313 }
314
315 /** Sets the time to stop flag */
316 public final void setTimeToStop( int flag ) {
317 timeToStop = true;
318 stopCode = flag;
319 synchronized( this ) {
320 notifyAll();
321 }
322 }
323
324
325 /**
326 * Load the icon for an agent off of disk. If the agent is class
327 * <code>edu.mit.media.hive.FooAgent</code> then this looks for
328 * <code>edu.mit.media.hive/icons/FooAgent.ppm</code> in the class
329 * path or Jar file. If that doesn't exist, then a default is
330 * substituted.
331 */
332 protected void loadIcon() {
333
334 java.io.InputStream is = null;
335 if( iconName.equals("") ){
336 icon = IconLoader.loadIcon( this.getClass() );
337 return;
338
339 } else {
340 is = Global.getConfigAsInputStream( iconName ); // Tries to load it from the user's home directory
341 if (is == null) {
342 Debug.notice("Trying to get user-specified icon from directory in CLASSPATH");
343 is = getClass().getResourceAsStream( iconName ); // Tries to load it from the current directory
344 }
345 }
346
347 if( is == null ) {
348 // Note: if these warnings are ever thrown, something has
349 // gone very wrong... (UnknownAgent.ppm not in
350 // hive/agent/icons)
351 Debug.warning( "No icon found for " + this.getClass().toString() + ", substituting " + IconLoader.defaultIconName );
352 is = getClass().getResourceAsStream( IconLoader.defaultIconName );
353 if( is == null ) {
354 Debug.critical( "Argh, " + IconLoader.defaultIconName + " doesn't exist. Agent icons are broken. Using blank icon." );
355
356 icon = new SerializableImage( new int[]{ 1 }, new java.awt.Dimension( 1, 1 ) );
357 return;
358 }
359 }
360
361 // OK, got the file as a stream, let's parse the PPM out and cast it into SerializableImage.
362 try {
363 icon = new PPM( is );
364 } catch( Exception ex ) {
365 Debug.critical( "Error loading agent's icon " + ex );
366 }
367 }
368
369 /** Return the SerializableImage object that is the agent's icon.
370 */
371 public SerializableImage getIcon() {
372 return icon;
373 }
374
375 /** Return the server I'm living on.
376 */
377 public RemoteCell getCell() {
378 return myCell;
379 }
380
381 /**
382 * Return the thread group that we are in
383 */
384 public ThreadGroup getThreadGroup() {
385 return agentThreadGroup;
386 }
387
388 /**
389 * Return a vector of all the agents that are connected to me
390 */
391 public Vector listAllIncomingConnections() {
392 Debug.warning( "There is no default connection list for an agent." );
393 return new Vector();
394 }
395
396 /**
397 * Return a vector of all that agents that i am connected to
398 */
399 public Vector listAllOutgoingConnections() {
400 Debug.warning( "There is no default connection list for an agent." );
401 return new Vector();
402 }
403
404
405 /**
406 * Tells the agent to configure itself from the DOM Node doc.
407 * Override this function to configure an agentfrom an XML file.
408 * This method is called by Hive if the agent was created through
409 * The user's ~/.hive/agentConfig file. If you want to load a
410 * configuration by hand, see Global.getConfigAsXMLDocument().
411 *
412 * @param doc The DOM Document (root node)
413 */
414 public void configure( Document doc ) throws RemoteException {}
415
416
417 /**
418 ** Add a popup menu item or non-graphical equivalent to the
419 ** agent's behavior.
420 **/
421 public void addActionCommand( ActionCommand com ) {
422 commandList.addElement(com.getName());
423 commands.put(com.getName(), com);
424 }
425
426 /**
427 ** Invoke an action command by name.
428 ** Currently, this will only be called in a
429 ** baseUICanvas or its non-graphical equivalent.
430 **/
431 public boolean invokeActionCommand( String com ) {
432 if( commands.containsKey( com ) ) {
433 ((ActionCommand)commands.get( com )).invoke();
434 return true;
435 }
436 return false;
437 }
438
439 /**
440 ** Get the names of the ActionCommands this agent uses in the
441 ** order in which they were entered.
442 **/
443 public Vector getActionCommands() { return (commandList); }
444
445
446 /**
447 * A convenience method to return the ComponentManagerShadow or
448 * throws an exception if one is not present.
449 */
450 public ComponentManagerShadow getComponentManagerShadow()
451 throws ShadowNotFoundException {
452 ComponentManagerShadow acms = (ComponentManagerShadow)myCell.getShadowDB().getShadow( "edu.mit.media.hive.shadow.cell.ComponentManagerShadow" );
453 if( acms == null ) {
454 throw new ShadowNotFoundException();
455 }
456 return acms;
457 }
458
459 /**
460 * Handle serialization requests.
461 */
462 private void writeObject( ObjectOutputStream out )
463 throws IOException {
464 out.defaultWriteObject();
465 }
466
467 /**
468 * Handle deserialization
469 */
470 private void readObject( ObjectInputStream in )
471 throws IOException, ClassNotFoundException {
472 in.defaultReadObject();
473 }
474
475
476 /**
477 * be careful when overriding thismethod. deciding when an agent
478 * is ready is up to the agent's author, however the agent should
479 * -not- report that it is ready until after the setIsReady( true
480 * ) has been called on it -- when that method call has occured,
481 * it means that the agent has been listed in the server's
482 * "directory"
483 *
484 * @param flag true if this agent is ready
485 */
486 public final void setIsReady( boolean flag ) {
487 readyFlag = flag;
488 synchronized( this ) {
489 notifyAll();
490 }
491 }
492
493 /**
494 * this flag tells another object whether this agent is ready to
495 * do its stuff. the semantics of being ready are left up to the
496 * agent's author, however the only restriction is that the agent
497 * should -not- report that it is ready until the server calls
498 * setIsReady( true ) on this agent
499 *
500 * @return if the agen t is "ready"
501 */
502 public boolean isReady() {
503 return readyFlag;
504 }
505
506 /**
507 * this method should block until the agent is ready -- it just
508 * sits in the wait method until it gets notified from the
509 * setIsReady method
510 */
511 public void blockUntilReady() {
512 while( !isReady() ) {
513 try {
514 synchronized( this ) {
515 wait();
516 }
517 } catch( InterruptedException error ) { }
518 }
519 }
520
521
522
523 /** Tell if the agent been told to stop. **/
524 public boolean isTimeToStop() {
525 return timeToStop;
526 }
527
528 }
529
530
531
532
533
534
535
536
537
538