View Javadoc

1   package cz.cuni.amis.pogamut.base.communication.worldview.impl;
2   
3   import java.util.LinkedList;
4   import java.util.Queue;
5   
6   import com.google.inject.Inject;
7   import com.google.inject.name.Named;
8   
9   import cz.cuni.amis.pogamut.base.communication.translator.event.IWorldChangeEvent;
10  import cz.cuni.amis.pogamut.base.communication.translator.event.IWorldEventWrapper;
11  import cz.cuni.amis.pogamut.base.communication.translator.event.IWorldObjectUpdateResult;
12  import cz.cuni.amis.pogamut.base.communication.translator.event.IWorldObjectUpdatedEvent;
13  import cz.cuni.amis.pogamut.base.communication.worldview.IWorldChangeEventInput;
14  import cz.cuni.amis.pogamut.base.communication.worldview.event.IWorldEvent;
15  import cz.cuni.amis.pogamut.base.communication.worldview.object.IWorldObject;
16  import cz.cuni.amis.pogamut.base.communication.worldview.object.event.WorldObjectDestroyedEvent;
17  import cz.cuni.amis.pogamut.base.communication.worldview.object.event.WorldObjectFirstEncounteredEvent;
18  import cz.cuni.amis.pogamut.base.communication.worldview.object.event.WorldObjectUpdatedEvent;
19  import cz.cuni.amis.pogamut.base.component.bus.IComponentBus;
20  import cz.cuni.amis.pogamut.base.component.bus.exception.ComponentNotRunningException;
21  import cz.cuni.amis.pogamut.base.component.bus.exception.ComponentPausedException;
22  import cz.cuni.amis.pogamut.base.component.controller.ComponentController;
23  import cz.cuni.amis.pogamut.base.component.controller.ComponentDependencies;
24  import cz.cuni.amis.pogamut.base.utils.guice.AgentScoped;
25  import cz.cuni.amis.pogamut.base.utils.logging.IAgentLogger;
26  import cz.cuni.amis.utils.NullCheck;
27  import cz.cuni.amis.utils.exception.PogamutException;
28  import java.util.logging.Level;
29  
30  /**
31   * Schema: "real" world | ... some communication ... | IWorldViewEventInput - EventDrivenWorldView | Agent (most probably listening for events)
32   * <p><p>
33   * {@link EventDrivenWorldView} assumes that everything is driven by the events that are received
34   * through {@link IWorldChangeEventInput} - those events surely must contains "new object appears event",
35   * "object update event", "object disappear event". Those three events are wrapped in one
36   * interface IWorldObjectUpdateEvent that has three types of behavior (see its javadoc).
37   * <p><p>
38   * The EDWV uses {@link IWorldChangeEventFilter}s to process incoming events. During the construction of the EDWV the filters
39   * should be registered into it (see method initFilters). Subclass this EDWV to provide a different set of filters.
40   * <p><p>
41   * Used filters:
42   * 
43   * TODO! finish the documentation
44   * 
45   * Handling of events (that is incoming):
46   * <ul>
47   * <li>event <b>is NOT</b> IWorldObjectUpdateEvent - the event is raised (if is instance of IWorldViewEvent, otherwise an exception is thrown)
48   * all listeners at this object for that type of event are informed sequentially as they were added</li>
49   * <li>event <b>IS</b> IWorldObjectUpdateEvent - the event is consumed (processed) by the EventDrivenWorldView
50   * the IWorldObjectUpdateEvent may have three already mentioned outcomes:
51   * 		<ol>
52   * 			<li>new object appears - an event WorldObjectAppearedEvent is raised</li>
53   * 			<li>object was updated - the EventDrivenWorldView will process this event silently, not raising any other events</li>
54   * 			<li>object disappeared - an even WorldObjectDisappearedEvent is raised</li>
55   *      </ol>
56   * </li>
57   * </ul>
58   * <p><p>
59   * <b>
60   * Note that the implementation of raising / receiving / notifying about event is
61   * strictly time-ordered. Every event is fully processed before another is raised/received.
62   * There is a possibility that raising one event may produce another one - in that case
63   * processing of this new one is postponed until the previous event has been fully processed
64   * (e.g. all listeners has been notified about it). That means that recursion of events is forbidden.
65   * <p><p>
66   * Note that we rely on method update() of the IWorldObjectUpdateEvent to be called
67   * as the first before the event is propagated further. An original object must be passed
68   * to the update() method and that method should save it as the original so the
69   * event can return correct object via getObject() method.
70   *
71   * @author Jimmy
72   */
73  @AgentScoped
74  public class EventDrivenWorldView extends AbstractWorldView {
75  	
76  	public static final String WORLDVIEW_DEPENDENCY = "EventDrivenWorldViewDependency";
77  
78      /**
79       * Flag that is telling us whether there is an event being processed or not.
80       * <p><p>
81       * It is managed only by notify() method - DO NOT MODIFY OUTSIDE IT!
82       */
83      protected boolean receiveEventProcessing = false;
84      
85      /**
86       * List of events we have to process.
87       * <p><p>
88       * It is managed only by notify() method - DO NOT MODIFY OUTSIDE IT!
89       */
90      protected LinkedList<IWorldChangeEvent> notifyEventsList = new LinkedList<IWorldChangeEvent>();
91  
92      @Inject
93      public EventDrivenWorldView(@Named(WORLDVIEW_DEPENDENCY) ComponentDependencies dependencies, IComponentBus bus, IAgentLogger log) {
94          super(dependencies, bus, log);
95      }
96      
97      /**
98       * Catches exceptions. If exception is caught, it calls {@link ComponentController}.fatalError() and this.kill(). 
99       */
100     @Override
101     protected void raiseEvent(IWorldEvent event) {
102     	try {
103     		if (log != null && log.isLoggable(Level.FINER)) log.finer("raising event " + event);
104     		super.raiseEvent(event);
105     	} catch (Exception e) {
106     		this.controller.fatalError("Exception raising event " + event, e);
107     		this.kill();
108     	}
109     }
110 
111     /**
112      * Used to process IWorldChangeEvent - it has to be either IWorldChangeEvent or IWorldObjectUpdateEvent. Forbids recursion.
113      * <p>
114      * DO NOT CALL SEPARATELY - should be called only from notifyEvent().
115      * <p><p>
116      * You may override it to provide event-specific processing behavior.
117      *
118      * @param event
119      */
120     protected void innerNotify(IWorldChangeEvent event) {    	
121     	NullCheck.check(event, "event");
122     	if (log.isLoggable(Level.FINEST)) {
123     		log.finest("processing " + event);
124     	}
125     	
126         if (event instanceof IWorldObjectUpdatedEvent) {
127             objectUpdatedEvent((IWorldObjectUpdatedEvent)event);
128         } else {
129             if (event instanceof IWorldEventWrapper) {
130                 raiseEvent(((IWorldEventWrapper) event).getWorldEvent());
131             } else
132             if (event instanceof IWorldEvent) {
133             	raiseEvent((IWorldEvent)event);
134             } else {
135                 throw new PogamutException("Unsupported event type received (" + event.getClass() + ").", this);
136             }
137         }
138     }
139     
140     /**
141      * Called from {@link EventDrivenWorldView#innerNotify(IWorldChangeEvent)} if the event is {@link IWorldObjectUpdatedEvent}
142      * to process it.
143      * 
144      * @param updateEvent
145      */
146     protected void objectUpdatedEvent(IWorldObjectUpdatedEvent updateEvent) {
147         IWorldObject obj = get(updateEvent.getId());
148         IWorldObjectUpdateResult updateResult = updateEvent.update(obj);
149         switch (updateResult.getResult()) {
150         case CREATED:            	
151             objectCreated(updateResult.getObject());
152             return;
153         case UPDATED:
154         	if (updateResult.getObject() != obj) {
155         		throw new PogamutException("Update event " + updateEvent + " does not returned the same instance of the object (result UPDATED).", this);
156         	}
157         	objectUpdated(obj);
158         	return;
159         case SAME:
160         	if (log.isLoggable(Level.FINEST)) {
161         		log.finest("no update for " + updateEvent);
162         	}
163         	return;
164         case DESTROYED:
165         	objectDestroyed(obj);
166             return;
167         default:
168         	throw new PogamutException("Unhandled object update result " + updateResult.getResult() + " for the object " + obj + ".", this);
169         }
170     }
171 
172     /**
173      * Must be called whenever an object was created, raises correct events.
174      * <p><p>
175      * Might be overridden to provide different behavior.
176      * @param obj
177      */
178     protected void objectCreated(IWorldObject obj) {
179     	addWorldObject(obj);
180     	raiseEvent(new WorldObjectFirstEncounteredEvent<IWorldObject>(obj, obj.getSimTime()));
181         objectUpdated(obj);
182     }
183     
184     /**
185      * Must be called whenever an object was updated - raises correct event.
186      * <p><p>
187      * Might be overridden to provide a mechanism that will forbid
188      * update of certain objects (like items that can't move).
189      *
190      * @param obj
191      */
192     protected void objectUpdated(IWorldObject obj) {
193         raiseEvent(new WorldObjectUpdatedEvent<IWorldObject>(obj, obj.getSimTime()));
194     }
195     
196     /**
197      * Must be called whenever an object was destroyed - raises correct events.
198      * <p><p>
199      * Might be overriden to provide different behavior.
200      * 
201      * @param obj
202      */
203     protected void objectDestroyed(IWorldObject obj) {
204     	removeWorldObject(obj);
205         raiseEvent(new WorldObjectDestroyedEvent<IWorldObject>(obj, obj.getSimTime()));        
206     }
207 
208     @Override
209     public synchronized void notify(IWorldChangeEvent event) throws ComponentNotRunningException, ComponentPausedException {
210     	if (isPaused()) {
211     		throw new ComponentPausedException(controller.getState().getFlag(), this);
212     	}
213     	if (!isRunning()) {
214     		throw new ComponentNotRunningException(controller.getState().getFlag(), this);
215     	}
216 
217         // is this method recursively called?
218         if (receiveEventProcessing) {
219             // yes it is -> that means the previous event has not been
220             // processed! ... store this event and allows the previous one
221             // to be fully processed (e.g. postpone raising this event)
222             notifyEventsList.add(event);
223             return;
224         } else {
225             // no it is not ... so raise the flag that we're inside the method
226             receiveEventProcessing = true;
227         }
228         // process event
229         try {
230 	        innerNotify(event);
231 	        // check the events list size, do we have more events to process?
232 	        while (notifyEventsList.size() != 0) {
233 	            // yes -> do it!
234 	            innerNotify(notifyEventsList.poll());
235 	        }
236         } finally {
237         // all events has been processed, drop the flag that we're inside the method
238         	receiveEventProcessing = false;
239         }
240     }
241     
242     @Override
243     public synchronized void notifyAfterPropagation(IWorldChangeEvent event) throws ComponentNotRunningException, ComponentPausedException {
244     	notifyEventsList.addFirst(event);
245     }
246     
247     @Override
248     public synchronized void notifyImmediately(IWorldChangeEvent event) throws ComponentNotRunningException, ComponentPausedException {
249     	innerNotify(event);	    
250     }
251 
252 }