package com.openxc.sinks; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import android.util.Log; import com.google.common.base.MoreObjects; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.openxc.NoValueException; import com.openxc.measurements.BaseMeasurement; import com.openxc.measurements.Measurement; import com.openxc.measurements.UnrecognizedMeasurementTypeException; import com.openxc.messages.KeyMatcher; import com.openxc.messages.KeyedMessage; import com.openxc.messages.SimpleVehicleMessage; import com.openxc.messages.VehicleMessage; /** * A data sink that sends new measurements of specific types to listeners. * * Applications requesting asynchronous updates for specific signals get their * values through this sink. */ public class MessageListenerSink extends AbstractQueuedCallbackSink { private final static String TAG = "MessageListenerSink"; // The non-persistent listeners will be removed after they receive their // first message. private Map<KeyMatcher, MessageListenerGroup> mMessageListeners = new HashMap<>(); private Multimap<Class<? extends Measurement>, Measurement.Listener> mMeasurementTypeListeners = HashMultimap.create(); private Multimap<Class<? extends VehicleMessage>, VehicleMessage.Listener> mMessageTypeListeners = HashMultimap.create(); private class MessageListenerGroup { ArrayList<VehicleMessage.Listener> mPersistentListeners = new ArrayList<>(); ArrayList<VehicleMessage.Listener> mListeners = new ArrayList<>(); void add(VehicleMessage.Listener listener, boolean persist) { if (persist) { mPersistentListeners.add(listener); } else { mListeners.add(listener); } } void removePersistent(VehicleMessage.Listener listener) { mPersistentListeners.remove(listener); } void receive(VehicleMessage message) { for (VehicleMessage.Listener listener : mPersistentListeners) { listener.receive(message); } for (VehicleMessage.Listener listener : mListeners) { listener.receive(message); } mListeners = new ArrayList<>(); //delete all non-persistent } boolean isEmpty() { return mPersistentListeners.isEmpty() && mListeners.isEmpty(); } } public MessageListenerSink() { super(); } public synchronized void register(KeyMatcher matcher, VehicleMessage.Listener listener, boolean persist) { MessageListenerGroup group = mMessageListeners.get(matcher); if (group == null) { group = new MessageListenerGroup(); mMessageListeners.put(matcher, group); } group.add(listener, persist); } public synchronized void register(KeyMatcher matcher, VehicleMessage.Listener listener) { register(matcher, listener, true); } public synchronized void register( Class<? extends VehicleMessage> messageType, VehicleMessage.Listener listener) { mMessageTypeListeners.put(messageType, listener); } public void register(Class<? extends Measurement> measurementType, Measurement.Listener listener) { try { // A bit of a hack to cache this measurement's ID field so we // can deserialize incoming measurements of this type. Why don't we // have a getId() in the Measurement interface? Ah, because it would // have to be static and you can't have static methods in an // interface. It would work if we were passed an instance of the // measurement in this function, but we don't really have that when // adding a listener. BaseMeasurement.getKeyForMeasurement(measurementType); } catch(UnrecognizedMeasurementTypeException e) { } mMeasurementTypeListeners.put(measurementType, listener); } public synchronized void unregister( Class<? extends Measurement> measurementType, Measurement.Listener listener) { mMeasurementTypeListeners.remove(measurementType, listener); } public synchronized void unregister( Class<? extends VehicleMessage> messageType, VehicleMessage.Listener listener) { mMessageTypeListeners.remove(messageType, listener); } public synchronized void unregister(KeyMatcher matcher, VehicleMessage.Listener listener) { MessageListenerGroup group = mMessageListeners.get(matcher); if (group != null) { group.removePersistent(listener); pruneListeners(matcher); } } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("numMessageListeners", mMessageListeners.size()) .add("numMessageTypeListeners", mMessageTypeListeners.size()) .add("numPersistentMessageListeners", getNumPersistentListeners()) .add("numMeasurementTypeListeners", mMeasurementTypeListeners.size()) .toString(); } private int getNumPersistentListeners() { int sum = 0; for (KeyMatcher matcher : mMessageListeners.keySet()) { sum += mMessageListeners.get(matcher).mPersistentListeners.size(); } return sum; } private void pruneListeners(KeyMatcher matcher) { MessageListenerGroup group = mMessageListeners.get(matcher); if (group != null && group.isEmpty()) { mMessageListeners.remove(matcher); } } @Override protected synchronized void propagateMessage(VehicleMessage message) { if (message instanceof KeyedMessage) { Set<KeyMatcher> matchedKeys = new HashSet<>(); for (KeyMatcher matcher : mMessageListeners.keySet()) { if (matcher.matches(message.asKeyedMessage())) { MessageListenerGroup group = mMessageListeners.get(matcher); group.receive(message); matchedKeys.add(matcher); } } for (KeyMatcher matcher : matchedKeys) { pruneListeners(matcher); } if (message instanceof SimpleVehicleMessage) { propagateMeasurementFromMessage(message.asSimpleMessage()); } } if(mMessageTypeListeners.containsKey(message.getClass())) { for(VehicleMessage.Listener listener : mMessageTypeListeners.get(message.getClass())) { listener.receive(message); } } } private synchronized void propagateMeasurementFromMessage( SimpleVehicleMessage message) { try { Measurement measurement = BaseMeasurement.getMeasurementFromMessage(message); if(mMeasurementTypeListeners.containsKey(measurement.getClass())) { for(Measurement.Listener listener : mMeasurementTypeListeners.get(measurement.getClass())) { listener.receive(measurement); } } } catch(UnrecognizedMeasurementTypeException e) { // The message is not a recognized Measurement, we don't propagate // it as a Measurement (only as a Message, which is handled // earlier). } catch(NoValueException e) { Log.w(TAG, "Received notification for a blank measurement", e); } } }