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 }