View Javadoc
1   package org.opentrafficsim.imb.transceiver;
2   
3   import java.rmi.RemoteException;
4   import java.util.HashMap;
5   import java.util.Map;
6   
7   import org.opentrafficsim.core.dsol.OTSDEVSSimulatorInterface;
8   import org.opentrafficsim.imb.IMBException;
9   import org.opentrafficsim.imb.connector.Connector;
10  
11  import nl.tno.imb.TByteBuffer;
12  import nl.tudelft.simulation.event.EventInterface;
13  import nl.tudelft.simulation.event.EventProducer;
14  import nl.tudelft.simulation.event.EventProducerInterface;
15  import nl.tudelft.simulation.event.EventType;
16  import nl.tudelft.simulation.language.Throw;
17  
18  /**
19   * Provide the basic implementation of a Transceiver from which targeted classes can extend.
20   * <p>
21   * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
22   * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
23   * <p>
24   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Sep 9, 2016 <br>
25   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
26   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
27   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
28   */
29  public abstract class AbstractTransceiver extends EventProducer implements EventTransceiver
30  {
31      /** */
32      private static final long serialVersionUID = 20160909L;
33  
34      /** An id to identify the channel, e.g., "GTU" or "Simulator Control". */
35      private final String id;
36  
37      /** The IMB connector through which this transceiver communicates. */
38      private final Connector connector;
39  
40      /** The simulator to schedule the incoming notifications on. */
41      private final OTSDEVSSimulatorInterface simulator;
42  
43      /** The map to indicate which IMB message handler to use for a given IMB message type. */
44      private Map<String, IMBMessageHandler> imbMessageHandlerMap = new HashMap<>();
45  
46      /** The map to indicate which OTS EventType is mapped to which IMB event name (String). */
47      private Map<EventType, String> otsToIMBMap = new HashMap<>();
48  
49      /** The map to indicate which Transformer to use for a given OTS EventType. */
50      private Map<EventType, OTSToIMBTransformer> otsTransformerMap = new HashMap<>();
51  
52      /**
53       * Construct a new AbstractTranceiver.
54       * @param id String; an id to identify the channel, e.g., "GTU" or "Simulator Control"
55       * @param connector Connector; the IMB connector through which this transceiver communicates
56       * @param simulator OTSDEVSSimulatorInterface; the simulator to schedule the incoming notifications on
57       * @throws NullPointerException in case one of the arguments is null.
58       */
59      public AbstractTransceiver(final String id, final Connector connector, final OTSDEVSSimulatorInterface simulator)
60      {
61          Throw.whenNull(connector, "Connector can not be null");
62          Throw.whenNull(id, "id can not be null");
63          Throw.whenNull(simulator, "simulator can not be null");
64          this.id = id;
65          this.connector = connector;
66          this.simulator = simulator;
67      }
68  
69      /**
70       * Make a connection from OTS to IMB, and and send a NEW message to IMB for the imbEventName with a corresponding payload.
71       * Store the transformer to use to create the CHANGE IMB messages. The Transceiver subscribes to the relevant information of
72       * the OTS EventProducer so it can send CHANGE messages from now on.
73       * @param producer EventProducerInterface; the OTS event producer that notifies this Transceiver about state changes
74       * @param eventType EventType; the event type that corresponds to the state change for this channel
75       * @param imbEventName String; the IMB event name for the message to send
76       * @param imbNewPayload Object[]; the information to send to IMB with the IMB NEW message
77       * @param transformer OTSToIMBTransformer; the transformer to use for create the IMB CHANGE events from an OTS Event content
78       * @throws NullPointerException in case one of the arguments is null.
79       * @throws IMBException in case the mapping from an EventType to an IMB event name is different from a previous time when a
80       *             mapping was registered for the same EventType (but for a different OTS EventProducer instance), when the
81       *             transformer for an EventType was changed a previous time when a mapping was registered, or when the
82       *             subscription to the OTS EventProducer fails.
83       */
84      public final void addOTSToIMBChannel(final EventProducerInterface producer, final EventType eventType,
85              final String imbEventName, Object[] imbNewPayload, final OTSToIMBTransformer transformer) throws IMBException
86      {
87          Throw.whenNull(producer, "producer cannot be null");
88          Throw.whenNull(eventType, "eventType cannot be null");
89          Throw.whenNull(imbEventName, "imbEventName cannot be null");
90          Throw.whenNull(imbNewPayload, "imbNewPayload cannot be null");
91          Throw.whenNull(transformer, "transformer cannot be null");
92          Throw.when(this.otsToIMBMap.containsKey(eventType) && !this.otsToIMBMap.get(eventType).equals(imbEventName),
93                  IMBException.class, "mapping of EventType to IMB name cannot be changed");
94          Throw.when(this.otsTransformerMap.containsKey(eventType) && !this.otsTransformerMap.get(eventType).equals(transformer),
95                  IMBException.class, "mapping of EventType to Transformer cannot be changed");
96  
97          try
98          {
99              this.connector.postIMBMessage(imbEventName, Connector.IMBEventType.NEW, imbNewPayload);
100             this.otsToIMBMap.put(eventType, imbEventName);
101             this.otsTransformerMap.put(eventType, transformer);
102             producer.addListener(this, eventType);
103         }
104         catch (RemoteException exception)
105         {
106             throw new IMBException(exception);
107         }
108     }
109 
110     /**
111      * Remove a connection from OTS to IMB, and and send a DELETE message to IMB for the imbEventName with a corresponding
112      * payload. The Transceiver subscription to the relevant information of the OTS EventProducer instance is removed. <br>
113      * Note that the mappings of EventType to IMB Event name and of the EventType to the transformer are not removed. There can
114      * be more instances of OTS EventProducer that use this channel. E.g., when all GTUs communicate through one channel using
115      * the same Transformer, the mappings should not be removed when one GTU leaves the model.
116      * @param producer EventProducerInterface; the OTS event producer to which we should stop listening
117      * @param eventType EventType; the event type that corresponds for this channel
118      * @param imbDeletePayload Object[]; the information to send to IMB with the IMB DELETE message
119      * @throws NullPointerException in case one of the arguments is null.
120      * @throws IMBException when the cancellation of the subscription to the OTS EventProducer fails, or when the EventType for
121      *             the channel was not registered with an addOTSToIMBChannel call.
122      */
123     public final void removeOTSToIMBChannel(final EventProducerInterface producer, final EventType eventType,
124             Object[] imbDeletePayload) throws IMBException
125     {
126         Throw.whenNull(producer, "producer cannot be null");
127         Throw.whenNull(eventType, "eventType cannot be null");
128         Throw.whenNull(imbDeletePayload, "imbDeletePayload cannot be null");
129         Throw.when(!this.otsToIMBMap.containsKey(eventType), IMBException.class, "EventType " + eventType
130                 + " for this channel was not registered with an addOTSToIMBChannel call");
131 
132         try
133         {
134             producer.removeListener(this, eventType);
135             this.connector.postIMBMessage(this.otsToIMBMap.get(eventType), Connector.IMBEventType.DELETE, imbDeletePayload);
136             // Do not implement this.otsToIMBMap.remove(eventType), as there may be more listeners for the same EventType.
137         }
138         catch (Exception exception)
139         {
140             throw new IMBException(exception);
141         }
142     }
143 
144     /** {@inheritDoc} */
145     @Override
146     public void notify(final EventInterface event) throws RemoteException
147     {
148         String imbEventName = this.otsToIMBMap.get(event.getType());
149         if (null != imbEventName)
150         {
151             // if (!event.getType().equals(GTU.MOVE_EVENT))
152             // {
153             // System.out.println("About to transmit to IMB event " + imbEventName + " " + event.getContent());
154             // }
155             try
156             {
157                 this.connector.postIMBMessage(imbEventName, Connector.IMBEventType.CHANGE,
158                         this.otsTransformerMap.get(event.getType()).transform(event));
159             }
160             catch (Exception exception)
161             {
162                 exception.printStackTrace();
163             }
164         }
165     }
166 
167     /**
168      * Register a new channel for sending an IMB message to an OTS EventListener. Note that the listeners are not registered
169      * directly as an EventListener with the addListener method. Instead, we directly call the notify(event) method on the
170      * listeners.
171      * @param imbEventName String; the name of the IMB event
172      * @param eventType EventType; the event type that the listener subscribes to
173      * @param imbToOTSTransformer IMBToOTSTransformer; the transformer that creates the event content and identifies the exact
174      *            listener on the basis of the IBM event payload, e.g., on the basis of an id within the payload
175      * @throws IMBException in case the registration fails
176      */
177     public void addIMBtoOTSChannel(final String imbEventName, final EventType eventType,
178             final IMBToOTSTransformer imbToOTSTransformer) throws IMBException
179     {
180         Throw.whenNull(imbEventName, "imbEventName cannot be null");
181         Throw.whenNull(eventType, "eventType cannot be null");
182         Throw.whenNull(imbToOTSTransformer, "imbToOTSTransformer cannot be null");
183 
184         this.imbMessageHandlerMap.put(imbEventName, new PubSubIMBMessageHandler(imbEventName, eventType, imbToOTSTransformer,
185                 this.simulator));
186         this.connector.register(imbEventName, this); // tell the connector we are interested in this IMB event
187     }
188 
189     /**
190      * Register that we are interested in an IMB payload, but do <b>not</b> register a listener or transformer.
191      * @param imbEventName String; the name of the IMB event
192      * @param imbMessageHandler IMBMessageHandler the message handler that takes care of the IMB message
193      * @throws IMBException in case registration fails
194      */
195     public void addIMBtoOTSChannel(final String imbEventName, final IMBMessageHandler imbMessageHandler) throws IMBException
196     {
197         Throw.whenNull(imbEventName, "imbEventName cannot be null");
198         Throw.whenNull(imbMessageHandler, "imbMessageHandler cannot be null");
199 
200         this.imbMessageHandlerMap.put(imbEventName, imbMessageHandler); // register the handler
201         this.connector.register(imbEventName, this); // tell the connector we are interested in this IMB event
202     }
203 
204     /** {@inheritDoc} */
205     @Override
206     public void handleMessageFromIMB(final String imbEventName, final TByteBuffer imbPayload) throws IMBException
207     {
208         Throw.when(!this.imbMessageHandlerMap.containsKey(imbEventName), IMBException.class,
209                 "Could not find IMB-to-OTS handler for IMB event name " + imbEventName);
210         this.imbMessageHandlerMap.get(imbEventName).handle(imbPayload);
211     }
212 
213     /** {@inheritDoc} */
214     @Override
215     public String getId()
216     {
217         return this.id;
218     }
219 
220     /** {@inheritDoc} */
221     @Override
222     public final Connector getConnector()
223     {
224         return this.connector;
225     }
226 
227     /** {@inheritDoc} */
228     @Override
229     @SuppressWarnings("checkstyle:designforextension")
230     public String toString()
231     {
232         return "AbstractTransceiver [id=" + this.id + ", connector=" + this.connector + "]";
233     }
234 
235     /**
236      * Retrieve the simulator.
237      * @return OTSDEVSSimulatorInterface simulator
238      */
239     public OTSDEVSSimulatorInterface getSimulator()
240     {
241         return this.simulator;
242     }
243 
244 }