From 16b69d4aeff29afe99e67c397d09a816c6a933f0 Mon Sep 17 00:00:00 2001 From: Denny Date: Sun, 15 Jan 2017 08:42:13 -0500 Subject: [PATCH] #90 [WIP] on redesigning the channel processing gui components and backing model. Simplifies channel state by introducing a new Metadata class that tracks all metadata for all decoders. --- src/channel/metadata/AliasedIdentifier.java | 22 +- .../metadata/ChannelMetadataModel.java | 18 +- .../metadata/ChannelMetadataPanel.java | 9 +- .../IAttributeChangeRequestListener.java | 26 + .../IAttributeChangeRequestProvider.java | 28 + src/channel/metadata/Metadata.java | 529 +++++++++++----- src/channel/metadata/MutableMetadata.java | 251 ++++++++ ...t.java => MutableMetadataChangeEvent.java} | 8 +- .../state/AlwaysUnsquelchedDecoderState.java | 124 ++-- src/channel/state/ChannelState.java | 43 +- src/channel/state/DecoderState.java | 356 +++++------ .../fleetsync2/Fleetsync2DecoderState.java | 599 +++++++++--------- 12 files changed, 1256 insertions(+), 757 deletions(-) create mode 100644 src/channel/metadata/IAttributeChangeRequestListener.java create mode 100644 src/channel/metadata/IAttributeChangeRequestProvider.java create mode 100644 src/channel/metadata/MutableMetadata.java rename src/channel/metadata/{MetadataChangeEvent.java => MutableMetadataChangeEvent.java} (87%) diff --git a/src/channel/metadata/AliasedIdentifier.java b/src/channel/metadata/AliasedIdentifier.java index 57db3f0d..ef71adf2 100644 --- a/src/channel/metadata/AliasedIdentifier.java +++ b/src/channel/metadata/AliasedIdentifier.java @@ -34,8 +34,10 @@ public class AliasedIdentifier /** * Resets alias and value to null + * + * Package Private - should only be changed within MutableMetadata */ - public void reset() + void reset() { mIdentifier = null; mAlias = null; @@ -51,8 +53,10 @@ public class AliasedIdentifier /** * Sets the identifier to the argument value + * + * Package Private - should only be changed within MutableMetadata */ - public void setIdentifier(String identifier) + void setIdentifier(String identifier) { mIdentifier = identifier; } @@ -75,8 +79,10 @@ public class AliasedIdentifier /** * Sets the alias to the argument + * + * Package Private - should only be changed within MutableMetadata */ - public void setAlias(Alias alias) + void setAlias(Alias alias) { mAlias = alias; } @@ -88,4 +94,14 @@ public class AliasedIdentifier { return mAlias != null; } + + public AliasedIdentifier copyOf() + { + AliasedIdentifier copy = new AliasedIdentifier(); + + copy.setAlias(mAlias); + copy.setIdentifier(new String(mIdentifier)); + + return copy; + } } diff --git a/src/channel/metadata/ChannelMetadataModel.java b/src/channel/metadata/ChannelMetadataModel.java index 2e6b7390..8b890782 100644 --- a/src/channel/metadata/ChannelMetadataModel.java +++ b/src/channel/metadata/ChannelMetadataModel.java @@ -27,7 +27,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -public class ChannelMetadataModel extends AbstractTableModel implements Listener +public class ChannelMetadataModel extends AbstractTableModel implements Listener { public static final int COLUMN_STATE = 0; public static final int COLUMN_PRIMARY = 1; @@ -40,10 +40,10 @@ public class ChannelMetadataModel extends AbstractTableModel implements Listener private static final String[] COLUMNS = {"State", "Primary", "Secondary", "Message", "Network", "Frequency", "Configuration"}; - private List mChannelMetadata = new ArrayList(); - private Map mMetadataChannelMap = new HashMap(); + private List mChannelMetadata = new ArrayList(); + private Map mMetadataChannelMap = new HashMap(); - public void add(Metadata metadata, Channel channel) + public void add(MutableMetadata metadata, Channel channel) { mChannelMetadata.add(metadata); mMetadataChannelMap.put(metadata, channel); @@ -55,7 +55,7 @@ public class ChannelMetadataModel extends AbstractTableModel implements Listener metadata.addListener(this); } - public void remove(Metadata metadata) + public void remove(MutableMetadata metadata) { metadata.removeListener(this); @@ -88,7 +88,7 @@ public class ChannelMetadataModel extends AbstractTableModel implements Listener @Override public Class getColumnClass(int columnIndex) { - return Metadata.class; + return MutableMetadata.class; } @Override @@ -98,13 +98,13 @@ public class ChannelMetadataModel extends AbstractTableModel implements Listener } @Override - public void receive(MetadataChangeEvent metadataChangeEvent) + public void receive(MutableMetadataChangeEvent mutableMetadataChangeEvent) { - int rowIndex = mChannelMetadata.indexOf(metadataChangeEvent.getMetadata()); + int rowIndex = mChannelMetadata.indexOf(mutableMetadataChangeEvent.getMetadata()); if(rowIndex >= 0) { - switch(metadataChangeEvent.getAttribute()) + switch(mutableMetadataChangeEvent.getAttribute()) { case CHANNEL_CONFIGURATION_LABEL_1: case CHANNEL_CONFIGURATION_LABEL_2: diff --git a/src/channel/metadata/ChannelMetadataPanel.java b/src/channel/metadata/ChannelMetadataPanel.java index e3fa68aa..6b8d0b49 100644 --- a/src/channel/metadata/ChannelMetadataPanel.java +++ b/src/channel/metadata/ChannelMetadataPanel.java @@ -24,7 +24,6 @@ import net.miginfocom.swing.MigLayout; import javax.swing.*; import javax.swing.event.ListSelectionListener; -import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; import java.awt.*; import java.text.DecimalFormat; @@ -120,7 +119,7 @@ public class ChannelMetadataPanel extends JPanel public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - Metadata metadata = (Metadata)value; + MutableMetadata metadata = (MutableMetadata)value; if(mAttribute1 != null) { @@ -187,7 +186,7 @@ public class ChannelMetadataPanel extends JPanel public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - Metadata metadata = (Metadata)value; + MutableMetadata metadata = (MutableMetadata)value; if(metadata.hasChannelFrequency()) { @@ -198,7 +197,7 @@ public class ChannelMetadataPanel extends JPanel mLabel1.setText(null); } - mLabel2.setText(metadata.getChannelID()); + mLabel2.setText(metadata.getChannelFrequencyLabel()); setBackground(isSelected ? table.getSelectionBackground() : table.getBackground()); @@ -224,7 +223,7 @@ public class ChannelMetadataPanel extends JPanel public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - Metadata metadata = (Metadata)value; + MutableMetadata metadata = (MutableMetadata)value; mLabel1.setText(metadata.getState().getDisplayValue()); diff --git a/src/channel/metadata/IAttributeChangeRequestListener.java b/src/channel/metadata/IAttributeChangeRequestListener.java new file mode 100644 index 00000000..b753569a --- /dev/null +++ b/src/channel/metadata/IAttributeChangeRequestListener.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * sdrtrunk + * Copyright (C) 2014-2017 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * + ******************************************************************************/ +package channel.metadata; + +import sample.Listener; + +public interface IAttributeChangeRequestListener +{ + public Listener getAttributeChangeRequestListener(); +} diff --git a/src/channel/metadata/IAttributeChangeRequestProvider.java b/src/channel/metadata/IAttributeChangeRequestProvider.java new file mode 100644 index 00000000..7be76f95 --- /dev/null +++ b/src/channel/metadata/IAttributeChangeRequestProvider.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * sdrtrunk + * Copyright (C) 2014-2017 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * + ******************************************************************************/ +package channel.metadata; + +import sample.Listener; + +public interface IAttributeChangeRequestProvider +{ + public void setAttributeChangeRequestListener(Listener listener); + + public void removeAttributeChangeRequestListener(); +} diff --git a/src/channel/metadata/Metadata.java b/src/channel/metadata/Metadata.java index 6540fe80..5d268ec4 100644 --- a/src/channel/metadata/Metadata.java +++ b/src/channel/metadata/Metadata.java @@ -19,234 +19,187 @@ package channel.metadata; import alias.Alias; +import alias.id.broadcast.BroadcastChannel; +import alias.id.priority.Priority; import channel.state.State; -import sample.Broadcaster; -import sample.Listener; +import module.decode.DecoderType; -public class Metadata implements Listener +import java.util.ArrayList; +import java.util.List; + +public class Metadata { - private State mState = State.IDLE; - private String mChannelConfigurationLabel1; - private String mChannelConfigurationLabel2; - private String mChannelID; - private long mChannelFrequency; - private AliasedIdentifier mNetworkID1 = new AliasedIdentifier(); - private AliasedIdentifier mNetworkID2 = new AliasedIdentifier(); + // Static unique metadata identifier tracking + private static int UNIQUE_METADATA_ID_GENERATOR = 0; - //Temporal attributes - private String mMessage; - private String mMessageType; - private AliasedIdentifier mPrimaryAddressFrom = new AliasedIdentifier(); - private AliasedIdentifier mPrimaryAddressTo = new AliasedIdentifier(); - private AliasedIdentifier mSecondaryAddressFrom = new AliasedIdentifier(); - private AliasedIdentifier mSecondaryAddressTo = new AliasedIdentifier(); + private int mMetadataID; - private Broadcaster mMetadataChangeEventBroadcaster = new Broadcaster<>(); + protected boolean mUpdated; + protected DecoderType mPrimaryDecoderType; + protected boolean mSelected; + protected State mState = State.IDLE; + protected String mChannelConfigurationLabel1; + protected String mChannelConfigurationLabel2; + protected String mChannelFrequencyLabel; + protected long mChannelFrequency; + protected AliasedIdentifier mNetworkID1 = new AliasedIdentifier(); + protected AliasedIdentifier mNetworkID2 = new AliasedIdentifier(); + + protected String mMessage; + protected String mMessageType; + protected AliasedIdentifier mPrimaryAddressFrom = new AliasedIdentifier(); + protected AliasedIdentifier mPrimaryAddressTo = new AliasedIdentifier(); + protected AliasedIdentifier mSecondaryAddressFrom = new AliasedIdentifier(); + protected AliasedIdentifier mSecondaryAddressTo = new AliasedIdentifier(); + + //Lazily constructed member variables. + private Integer mAudioPriority; + private Boolean mRecordable; + private List mBroadcastChannels; /** * Channel metadata. Contains all attributes that reflect the state and current attribute values for a channel * that is currently decoding. This metadata is intended to support any decoding channel gui components to - * graphically convey the current state of a decoding channel. + * graphically convey the current state of a decoding channel and to provide audio metadata */ public Metadata() { + mMetadataID = UNIQUE_METADATA_ID_GENERATOR++; } /** - * Sets the state of this metadata + * Protected constructor. Primarily used by the copyOf() method to create a metadata copy with the same ID */ - public void setState(State state) + protected Metadata(int metadataID) { - mState = state; - broadcast(Attribute.CHANNEL_STATE); + mMetadataID = metadataID; } /** - * Request to change an attribute value for this metadata. This is the primary method for updating all attributes - * in this metadata set. + * Unique string identifier for this metadata that is comprised of the channel ID and the primary TO address. + * + * This identifier can be used to uniquely identify a channel audio stream and the primary communicant. */ - @Override - public void receive(AttributeChangeRequest request) + public String getUniqueIdentifier() { - switch(request.getAttribute()) - { - case CHANNEL_CONFIGURATION_LABEL_1: - mChannelConfigurationLabel1 = request.getStringValue(); - broadcast(Attribute.CHANNEL_CONFIGURATION_LABEL_1); - break; - case CHANNEL_CONFIGURATION_LABEL_2: - mChannelConfigurationLabel2 = request.getStringValue(); - broadcast(Attribute.CHANNEL_CONFIGURATION_LABEL_2); - break; - case CHANNEL_FREQUENCY: - if(request.hasValue()) - { - mChannelFrequency = request.getLongValue(); - } - else - { - mChannelFrequency = 0; - } - broadcast(Attribute.CHANNEL_FREQUENCY); - break; - case CHANNEL_ID: - mChannelID = request.getStringValue(); - broadcast(Attribute.CHANNEL_ID); - break; - case MESSAGE: - mMessage = request.getStringValue(); - broadcast(Attribute.MESSAGE); - break; - case MESSAGE_TYPE: - mMessageType = request.getStringValue(); - broadcast(Attribute.MESSAGE_TYPE); - break; - case NETWORK_ID_1: - mNetworkID1.setIdentifier(request.getStringValue()); - mNetworkID1.setAlias(request.getAlias()); - broadcast(Attribute.NETWORK_ID_1); - break; - case NETWORK_ID_2: - mNetworkID2.setIdentifier(request.getStringValue()); - mNetworkID2.setAlias(request.getAlias()); - broadcast(Attribute.NETWORK_ID_2); - break; - case PRIMARY_ADDRESS_FROM: - mPrimaryAddressFrom.setIdentifier(request.getStringValue()); - mPrimaryAddressFrom.setAlias(request.getAlias()); - broadcast(Attribute.PRIMARY_ADDRESS_FROM); - break; - case PRIMARY_ADDRESS_TO: - mPrimaryAddressTo.setIdentifier(request.getStringValue()); - mPrimaryAddressTo.setAlias(request.getAlias()); - broadcast(Attribute.PRIMARY_ADDRESS_TO); - break; - case SECONDARY_ADDRESS_FROM: - mSecondaryAddressFrom.setIdentifier(request.getStringValue()); - mSecondaryAddressFrom.setAlias(request.getAlias()); - broadcast(Attribute.SECONDARY_ADDRESS_FROM); - break; - case SECONDARY_ADDRESS_TO: - mSecondaryAddressTo.setIdentifier(request.getStringValue()); - mSecondaryAddressTo.setAlias(request.getAlias()); - broadcast(Attribute.SECONDARY_ADDRESS_TO); - break; - default: - throw new IllegalArgumentException("Unrecognized Metadata Attribute: " + - request.getAttribute().name()); - } + return "SRC:" + mMetadataID + + " ID:" + (mPrimaryAddressTo.hasIdentifier() ? mPrimaryAddressTo.getIdentifier() : "UNKNOWN"); } /** - * Broadcasts to registered listeners that an attribute has changed for this metadata + * Indicates if any of the fields of this metadata have been updated since the last time a copy was made from this + * metadata. This method is primarily used by downstream audio playback and audio recording to signal when changes + * are made to the metadata that requires the downstream component to reinspect the metadata. + * + * This flag is reset to false immediately after a copy is made of this metadata via the copyOf() method. */ - private void broadcast(Attribute attribute) + public boolean isUpdated() { - mMetadataChangeEventBroadcaster.broadcast(new MetadataChangeEvent(this, attribute)); + return mUpdated; } /** - * Adds the listener to receive metadata change events for this channel metadata + * Audio Priority as the highest audio priority value from across the primary and secondary identifier aliases. + * + * A default priority will be used if there are no aliased values. If there is a conflict between audio priority + * and 'Do Not Monitor' among the aliases, the highest audio priority will be used (prefer to monitor). + * + * -1 Do Not Monitor + * 0 Channel Selected for immediate monitor + * 1 Highest Priority + * 100 Lowest Priority (default) + * + * @return audio priority */ - public void addListener(Listener listener) + public int getAudioPriority() { - mMetadataChangeEventBroadcaster.addListener(listener); + if(isSelected()) + { + return Priority.SELECTED_PRIORITY; + } + + if(mAudioPriority == null) + { + determineAudioPriority(); + } + + return mAudioPriority; } /** - * Removes the listener from receiving metadata change events for this channel metadata + * Indicates if the audio priority is set to 'Do Not Monitor' */ - public void removeListener(Listener listener) + public boolean isDoNotMonitor() { - mMetadataChangeEventBroadcaster.removeListener(listener); + return getAudioPriority() == Priority.DO_NOT_MONITOR; } /** - * Resets temporal attributes to null value. This only resets attributes that have a non-ull value in order to - * limit the number of change events that are produced. + * Indicates if any of the primary or secondary TO/FROM aliases are identified as recordable */ - public void resetTemporalAttributes() + public boolean isRecordable() { - if(hasMessage()) + if(mRecordable == null) { - mMessage = null; - broadcast(Attribute.MESSAGE); + determineRecordable(); } - if(hasMessageType()) - { - mMessageType = null; - broadcast(Attribute.MESSAGE_TYPE); - } - - if(mPrimaryAddressFrom.hasIdentifier()) - { - mPrimaryAddressFrom.reset(); - broadcast(Attribute.PRIMARY_ADDRESS_FROM); - } - - if(mPrimaryAddressTo.hasIdentifier()) - { - mPrimaryAddressTo.reset(); - broadcast(Attribute.PRIMARY_ADDRESS_TO); - } - - if(mSecondaryAddressFrom.hasIdentifier()) - { - mSecondaryAddressFrom.reset(); - broadcast(Attribute.SECONDARY_ADDRESS_FROM); - } - - if(mSecondaryAddressTo.hasIdentifier()) - { - mSecondaryAddressTo.reset(); - broadcast(Attribute.SECONDARY_ADDRESS_TO); - } + return mRecordable; } /** - * Resets all attributes to empty values + * Indicates that this metadata has been selected by the user and any audio produced by this channel will be set + * to the highest playback priority (immediate monitor). */ - public void resetAllAttributes() + public boolean isSelected() { - resetTemporalAttributes(); + return mSelected; + } - if(hasChannelConfigurationLabel1()) + /** + * List of de-duplicated broadcast channels aggregated from the primary and secondary TO/FROM aliases. + */ + public List getBroadcastChannels() + { + if(mBroadcastChannels == null) { - mChannelConfigurationLabel1 = null; - broadcast(Attribute.CHANNEL_CONFIGURATION_LABEL_1); + determineBroadcastChannels(); } - if(hasChannelConfigurationLabel2()) - { - mChannelConfigurationLabel2 = null; - broadcast(Attribute.CHANNEL_CONFIGURATION_LABEL_2); - } + return mBroadcastChannels; + } - if(hasChannelID()) - { - mChannelID = null; - broadcast(Attribute.CHANNEL_ID); - } + /** + * Indicates if this metadata is streamable, meaning that it contains one ore more broadcast channels + */ + public boolean isStreamable() + { + return !getBroadcastChannels().isEmpty(); + } - if(hasChannelFrequency()) - { - mChannelFrequency = 0; - broadcast(Attribute.CHANNEL_FREQUENCY); - } + /** + * Unique identifier for the source channel that produces this metadata + */ + public int getMetadataID() + { + return mMetadataID; + } - if(mNetworkID1.hasIdentifier()) - { - mNetworkID1.reset(); - broadcast(Attribute.NETWORK_ID_1); - } + /** + * Decoder type (ie protocol) for the primary decoder + */ + public DecoderType getPrimaryDecoderType() + { + return mPrimaryDecoderType; + } - if(mNetworkID2.hasIdentifier()) - { - mNetworkID2.reset(); - broadcast(Attribute.NETWORK_ID_2); - } + /** + * Indicates if this metadata has a primary decoder type defined + */ + public boolean hasPrimaryDecoderType() + { + return mPrimaryDecoderType != null; } /** @@ -285,7 +238,7 @@ public class Metadata implements Listener case CHANNEL_CONFIGURATION_LABEL_2: return mChannelConfigurationLabel2; case CHANNEL_ID: - return mChannelID; + return mChannelFrequencyLabel; case CHANNEL_STATE: return mState.getDisplayValue(); case MESSAGE: @@ -352,17 +305,17 @@ public class Metadata implements Listener /** * Channel ID or Channel Number */ - public String getChannelID() + public String getChannelFrequencyLabel() { - return mChannelID; + return mChannelFrequencyLabel; } /** * Indicates if there is a non-null, non-empty channel identifier value */ - public boolean hasChannelID() + public boolean hasChannelFrequencyLabel() { - return mChannelID != null && !mChannelID.isEmpty(); + return mChannelFrequencyLabel != null && !mChannelFrequencyLabel.isEmpty(); } /** @@ -460,4 +413,232 @@ public class Metadata implements Listener { return mSecondaryAddressTo; } + + /** + * Determines the audio priority from across the primary and secondary TO/FROM aliases when they exist + */ + private void determineAudioPriority() + { + int priority = Priority.DEFAULT_PRIORITY; + + boolean doNotMonitorFound = false; + boolean audioPriorityFound = false; + + if(getPrimaryAddressTo().hasAlias()) + { + Alias primaryTo = getPrimaryAddressTo().getAlias(); + + if(primaryTo.hasCallPriority()) + { + if(primaryTo.getCallPriority() == Priority.DO_NOT_MONITOR) + { + doNotMonitorFound = true; + } + else + { + audioPriorityFound = true; + + if(primaryTo.getCallPriority() < priority) + { + priority = primaryTo.getCallPriority(); + } + } + } + } + + if(getPrimaryAddressFrom().hasAlias()) + { + Alias primaryFrom = getPrimaryAddressFrom().getAlias(); + + if(primaryFrom.hasCallPriority()) + { + if(primaryFrom.getCallPriority() == Priority.DO_NOT_MONITOR) + { + doNotMonitorFound = true; + } + else + { + audioPriorityFound = true; + + if(primaryFrom.getCallPriority() < priority) + { + priority = primaryFrom.getCallPriority(); + } + } + } + } + + if(getSecondaryAddressTo().hasAlias()) + { + Alias secondaryTo = getSecondaryAddressTo().getAlias(); + + if(secondaryTo.hasCallPriority()) + { + if(secondaryTo.getCallPriority() == Priority.DO_NOT_MONITOR) + { + doNotMonitorFound = true; + } + else + { + audioPriorityFound = true; + + if(secondaryTo.getCallPriority() < priority) + { + priority = secondaryTo.getCallPriority(); + } + } + } + } + + if(getSecondaryAddressFrom().hasAlias()) + { + Alias secondaryFrom = getSecondaryAddressFrom().getAlias(); + + if(secondaryFrom.hasCallPriority()) + { + if(secondaryFrom.getCallPriority() == Priority.DO_NOT_MONITOR) + { + doNotMonitorFound = true; + } + else + { + audioPriorityFound = true; + + if(secondaryFrom.getCallPriority() < priority) + { + priority = secondaryFrom.getCallPriority(); + } + } + } + } + + //If we found a 'Do Not Monitor' and no other audio priority, then set to do not monitor + if(doNotMonitorFound && !audioPriorityFound) + { + mAudioPriority = Priority.DO_NOT_MONITOR; + } + else + { + mAudioPriority = priority; + } + } + + /** + * Determines if any of the primary or secondary to/from aliases are recordable. + */ + private void determineRecordable() + { + mRecordable = false; + + if(mPrimaryAddressTo.hasAlias() && mPrimaryAddressTo.getAlias().isRecordable()) + { + mRecordable = true; + return; + } + + if(mPrimaryAddressFrom.hasAlias() && mPrimaryAddressFrom.getAlias().isRecordable()) + { + mRecordable = true; + return; + } + + if(mSecondaryAddressTo.hasAlias() && mSecondaryAddressTo.getAlias().isRecordable()) + { + mRecordable = true; + return; + } + + if(mSecondaryAddressFrom.hasAlias() && mSecondaryAddressFrom.getAlias().isRecordable()) + { + mRecordable = true; + return; + } + } + + /** + * Aggregates a de-duplicated list of broadcast channels from among the primary/secondary to/from aliases + */ + private void determineBroadcastChannels() + { + mBroadcastChannels = new ArrayList<>(); + + if(mPrimaryAddressTo.hasAlias() && mPrimaryAddressTo.getAlias().isStreamable()) + { + for(BroadcastChannel broadcastChannel: mPrimaryAddressTo.getAlias().getBroadcastChannels()) + { + if(!mBroadcastChannels.contains(broadcastChannel)) + { + mBroadcastChannels.add(broadcastChannel); + } + } + } + + if(mPrimaryAddressFrom.hasAlias() && mPrimaryAddressFrom.getAlias().isStreamable()) + { + for(BroadcastChannel broadcastChannel: mPrimaryAddressFrom.getAlias().getBroadcastChannels()) + { + if(!mBroadcastChannels.contains(broadcastChannel)) + { + mBroadcastChannels.add(broadcastChannel); + } + } + } + + if(mSecondaryAddressTo.hasAlias() && mSecondaryAddressTo.getAlias().isStreamable()) + { + for(BroadcastChannel broadcastChannel: mSecondaryAddressTo.getAlias().getBroadcastChannels()) + { + if(!mBroadcastChannels.contains(broadcastChannel)) + { + mBroadcastChannels.add(broadcastChannel); + } + } + } + + if(mSecondaryAddressFrom.hasAlias() && mSecondaryAddressFrom.getAlias().isStreamable()) + { + for(BroadcastChannel broadcastChannel: mSecondaryAddressFrom.getAlias().getBroadcastChannels()) + { + if(!mBroadcastChannels.contains(broadcastChannel)) + { + mBroadcastChannels.add(broadcastChannel); + } + } + } + } + + /** + * Creates a deep/full snapshot copy of this metadata set. + * + * The streamable, recordable, audio priority and broadcast channels are not copied. These are lazily determined + * when initially requested. + */ + public Metadata copyOf() + { + Metadata copy = new Metadata(mMetadataID); + + copy.mState = mState; + copy.mPrimaryDecoderType = mPrimaryDecoderType; + copy.mChannelFrequency = mChannelFrequency; + + copy.mChannelConfigurationLabel1 = hasChannelConfigurationLabel1() ? new String(mChannelConfigurationLabel1) : null; + copy.mChannelConfigurationLabel2 = hasChannelConfigurationLabel2() ? new String(mChannelConfigurationLabel2) : null; + copy.mChannelFrequencyLabel = hasChannelFrequencyLabel() ? new String(mChannelFrequencyLabel) : null; + copy.mMessage = hasMessage() ? new String(mMessage) : null; + copy.mMessageType = hasMessageType() ? new String(mMessageType) : null; + + copy.mNetworkID1 = mNetworkID1.copyOf(); + copy.mNetworkID2 = mNetworkID2.copyOf(); + copy.mPrimaryAddressFrom = mPrimaryAddressFrom.copyOf(); + copy.mPrimaryAddressTo = mPrimaryAddressTo.copyOf(); + copy.mSecondaryAddressFrom = mSecondaryAddressFrom.copyOf(); + copy.mSecondaryAddressTo = mSecondaryAddressTo.copyOf(); + + copy.mUpdated = mUpdated; + + //Reset the updated flag + mUpdated = false; + + return copy; + } } diff --git a/src/channel/metadata/MutableMetadata.java b/src/channel/metadata/MutableMetadata.java new file mode 100644 index 00000000..7a4bb15d --- /dev/null +++ b/src/channel/metadata/MutableMetadata.java @@ -0,0 +1,251 @@ +/******************************************************************************* + * sdrtrunk + * Copyright (C) 2014-2017 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * + ******************************************************************************/ +package channel.metadata; + +import channel.state.State; +import module.decode.DecoderType; +import sample.Broadcaster; +import sample.Listener; + +public class MutableMetadata extends Metadata implements Listener +{ + private Broadcaster mMetadataChangeEventBroadcaster = new Broadcaster<>(); + + /** + * Mutable channel metadata. Contains all attributes that reflect the state and current attribute values for a + * channel that is currently decoding. This metadata is intended to support any decoding channel gui components to + * graphically convey the current state of a decoding channel and to provide audio metadata + */ + public MutableMetadata() + { + super(); + } + + /** + * Sets the selected state for this metadata. Selected indicates that the user has selected this metadata for + * immediate monitor in the audio panel. When true, this overrides the audio priority for any audio produced + * by the channel and sets that audio to the highest priority. + * + * Any component that toggles the selected state for this metadata is also responsible for clearing the flag. + */ + public void setSelected(boolean selected) + { + mSelected = selected; + } + + /** + * Sets the primary decoder type + */ + public void setPrimaryDecoderType(DecoderType decoderType) + { + mPrimaryDecoderType = decoderType; + } + + /** + * Request to change an attribute value for this metadata. This is the primary method for updating all attributes + * in this metadata set. + */ + @Override + public void receive(AttributeChangeRequest request) + { + switch(request.getAttribute()) + { + case CHANNEL_CONFIGURATION_LABEL_1: + mChannelConfigurationLabel1 = request.getStringValue(); + broadcast(Attribute.CHANNEL_CONFIGURATION_LABEL_1); + break; + case CHANNEL_CONFIGURATION_LABEL_2: + mChannelConfigurationLabel2 = request.getStringValue(); + broadcast(Attribute.CHANNEL_CONFIGURATION_LABEL_2); + break; + case CHANNEL_FREQUENCY: + if(request.hasValue()) + { + mChannelFrequency = request.getLongValue(); + } + else + { + mChannelFrequency = 0; + } + broadcast(Attribute.CHANNEL_FREQUENCY); + break; + case CHANNEL_ID: + mChannelFrequencyLabel = request.getStringValue(); + broadcast(Attribute.CHANNEL_ID); + break; + case MESSAGE: + mMessage = request.getStringValue(); + broadcast(Attribute.MESSAGE); + break; + case MESSAGE_TYPE: + mMessageType = request.getStringValue(); + broadcast(Attribute.MESSAGE_TYPE); + break; + case NETWORK_ID_1: + mNetworkID1.setIdentifier(request.getStringValue()); + mNetworkID1.setAlias(request.getAlias()); + broadcast(Attribute.NETWORK_ID_1); + break; + case NETWORK_ID_2: + mNetworkID2.setIdentifier(request.getStringValue()); + mNetworkID2.setAlias(request.getAlias()); + broadcast(Attribute.NETWORK_ID_2); + break; + case PRIMARY_ADDRESS_FROM: + mPrimaryAddressFrom.setIdentifier(request.getStringValue()); + mPrimaryAddressFrom.setAlias(request.getAlias()); + broadcast(Attribute.PRIMARY_ADDRESS_FROM); + break; + case PRIMARY_ADDRESS_TO: + mPrimaryAddressTo.setIdentifier(request.getStringValue()); + mPrimaryAddressTo.setAlias(request.getAlias()); + broadcast(Attribute.PRIMARY_ADDRESS_TO); + break; + case SECONDARY_ADDRESS_FROM: + mSecondaryAddressFrom.setIdentifier(request.getStringValue()); + mSecondaryAddressFrom.setAlias(request.getAlias()); + broadcast(Attribute.SECONDARY_ADDRESS_FROM); + break; + case SECONDARY_ADDRESS_TO: + mSecondaryAddressTo.setIdentifier(request.getStringValue()); + mSecondaryAddressTo.setAlias(request.getAlias()); + broadcast(Attribute.SECONDARY_ADDRESS_TO); + break; + case CHANNEL_STATE: + mState = (State)request.getValue(); + broadcast(Attribute.CHANNEL_STATE); + break; + default: + throw new IllegalArgumentException("Unrecognized Metadata Attribute: " + + request.getAttribute().name()); + } + } + + /** + * Resets temporal attributes to null value. This only resets attributes that have a non-ull value in order to + * limit the number of change events that are produced. + */ + public void resetTemporalAttributes() + { + if(hasMessage()) + { + mMessage = null; + broadcast(Attribute.MESSAGE); + } + + if(hasMessageType()) + { + mMessageType = null; + broadcast(Attribute.MESSAGE_TYPE); + } + + if(mPrimaryAddressFrom.hasIdentifier()) + { + mPrimaryAddressFrom.reset(); + broadcast(Attribute.PRIMARY_ADDRESS_FROM); + } + + if(mPrimaryAddressTo.hasIdentifier()) + { + mPrimaryAddressTo.reset(); + broadcast(Attribute.PRIMARY_ADDRESS_TO); + } + + if(mSecondaryAddressFrom.hasIdentifier()) + { + mSecondaryAddressFrom.reset(); + broadcast(Attribute.SECONDARY_ADDRESS_FROM); + } + + if(mSecondaryAddressTo.hasIdentifier()) + { + mSecondaryAddressTo.reset(); + broadcast(Attribute.SECONDARY_ADDRESS_TO); + } + } + + /** + * Resets all attributes to empty values + */ + public void resetAllAttributes() + { + resetTemporalAttributes(); + + if(hasChannelConfigurationLabel1()) + { + mChannelConfigurationLabel1 = null; + broadcast(Attribute.CHANNEL_CONFIGURATION_LABEL_1); + } + + if(hasChannelConfigurationLabel2()) + { + mChannelConfigurationLabel2 = null; + broadcast(Attribute.CHANNEL_CONFIGURATION_LABEL_2); + } + + if(hasChannelFrequencyLabel()) + { + mChannelFrequencyLabel = null; + broadcast(Attribute.CHANNEL_ID); + } + + if(hasChannelFrequency()) + { + mChannelFrequency = 0; + broadcast(Attribute.CHANNEL_FREQUENCY); + } + + if(mNetworkID1.hasIdentifier()) + { + mNetworkID1.reset(); + broadcast(Attribute.NETWORK_ID_1); + } + + if(mNetworkID2.hasIdentifier()) + { + mNetworkID2.reset(); + broadcast(Attribute.NETWORK_ID_2); + } + } + + + /** + * Broadcasts to registered listeners that an attribute has changed for this metadata + */ + private void broadcast(Attribute attribute) + { + mMetadataChangeEventBroadcaster.broadcast(new MutableMetadataChangeEvent(this, attribute)); + } + + /** + * Adds the listener to receive metadata change events for this channel metadata + */ + public void addListener(Listener listener) + { + mMetadataChangeEventBroadcaster.addListener(listener); + } + + /** + * Removes the listener from receiving metadata change events for this channel metadata + */ + public void removeListener(Listener listener) + { + mMetadataChangeEventBroadcaster.removeListener(listener); + } +} diff --git a/src/channel/metadata/MetadataChangeEvent.java b/src/channel/metadata/MutableMetadataChangeEvent.java similarity index 87% rename from src/channel/metadata/MetadataChangeEvent.java rename to src/channel/metadata/MutableMetadataChangeEvent.java index 123adb08..41d4e911 100644 --- a/src/channel/metadata/MetadataChangeEvent.java +++ b/src/channel/metadata/MutableMetadataChangeEvent.java @@ -18,9 +18,9 @@ ******************************************************************************/ package channel.metadata; -public class MetadataChangeEvent +public class MutableMetadataChangeEvent { - private Metadata mMetadata; + private MutableMetadata mMetadata; private Attribute mAttribute; /** @@ -29,7 +29,7 @@ public class MetadataChangeEvent * @param metadata where the change occurred * @param attribute that changed */ - public MetadataChangeEvent(Metadata metadata, Attribute attribute) + public MutableMetadataChangeEvent(MutableMetadata metadata, Attribute attribute) { mMetadata = metadata; mAttribute = attribute; @@ -38,7 +38,7 @@ public class MetadataChangeEvent /** * Channel metadata where the change occurred */ - public Metadata getMetadata() + public MutableMetadata getMetadata() { return mMetadata; } diff --git a/src/channel/state/AlwaysUnsquelchedDecoderState.java b/src/channel/state/AlwaysUnsquelchedDecoderState.java index 3d65f1ed..485e2cd1 100644 --- a/src/channel/state/AlwaysUnsquelchedDecoderState.java +++ b/src/channel/state/AlwaysUnsquelchedDecoderState.java @@ -1,29 +1,29 @@ /******************************************************************************* * SDR Trunk * Copyright (C) 2014,2015 Dennis Sheirer - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see ******************************************************************************/ package channel.state; -import java.util.concurrent.ScheduledExecutorService; - +import channel.metadata.Attribute; +import channel.metadata.AttributeChangeRequest; +import channel.state.DecoderStateEvent.Event; import message.Message; import module.decode.DecoderType; -import channel.state.DecoderStateEvent.Event; -import audio.metadata.Metadata; -import audio.metadata.MetadataType; + +import java.util.concurrent.ScheduledExecutorService; /** * Basic decoder channel state - provides the minimum channel state functionality @@ -31,68 +31,68 @@ import audio.metadata.MetadataType; */ public class AlwaysUnsquelchedDecoderState extends DecoderState { - private DecoderType mDecoderType; - private String mChannelName; - - public AlwaysUnsquelchedDecoderState( DecoderType decoderType, String channelName ) - { - super( null ); - - mDecoderType = decoderType; - mChannelName = channelName; - } - - @Override - public void init() - { - } - - @Override - public void reset() - { - /* No reset actions needed */ - } + private DecoderType mDecoderType; + private String mChannelName; - @Override - public String getActivitySummary() + public AlwaysUnsquelchedDecoderState(DecoderType decoderType, String channelName) { - StringBuilder sb = new StringBuilder(); - - sb.append( "Activity Summary\n" ); - sb.append( "\tDecoder:\t" ); - sb.append( mDecoderType ); - sb.append( "\n\n" ); - - return sb.toString(); + super(null); + + mDecoderType = decoderType; + mChannelName = channelName; } - @Override - public void receive( Message t ) - { + @Override + public void init() + { + } + + @Override + public void reset() + { + /* No reset actions needed */ + } + + @Override + public String getActivitySummary() + { + StringBuilder sb = new StringBuilder(); + + sb.append("Activity Summary\n"); + sb.append("\tDecoder:\t"); + sb.append(mDecoderType); + sb.append("\n\n"); + + return sb.toString(); + } + + @Override + public void receive(Message t) + { /* Not implemented */ - } + } - @Override - public void receiveDecoderStateEvent( DecoderStateEvent event ) - { + @Override + public void receiveDecoderStateEvent(DecoderStateEvent event) + { /* Not implemented */ - } + } - @Override - public DecoderType getDecoderType() - { - return mDecoderType; - } + @Override + public DecoderType getDecoderType() + { + return mDecoderType; + } - @Override - public void start( ScheduledExecutorService executor ) - { - broadcast( new Metadata( MetadataType.TO, mChannelName, false ) ); - broadcast( new DecoderStateEvent( this, Event.ALWAYS_UNSQUELCH, State.IDLE ) ); - } + @Override + public void start(ScheduledExecutorService executor) + { + broadcast(new AttributeChangeRequest(Attribute.PRIMARY_ADDRESS_TO, mChannelName)); + broadcast(new DecoderStateEvent(this, Event.ALWAYS_UNSQUELCH, State.IDLE)); + } - @Override - public void stop() - { - } + @Override + public void stop() + { + } } diff --git a/src/channel/state/ChannelState.java b/src/channel/state/ChannelState.java index 1188c3ac..56c57da4 100644 --- a/src/channel/state/ChannelState.java +++ b/src/channel/state/ChannelState.java @@ -23,6 +23,9 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import channel.metadata.Attribute; +import channel.metadata.AttributeChangeRequest; +import channel.metadata.IAttributeChangeRequestListener; +import channel.metadata.MutableMetadata; import channel.traffic.TrafficChannelManager; import module.Module; import module.decode.config.DecodeConfiguration; @@ -43,14 +46,16 @@ import audio.squelch.SquelchState; import controller.channel.Channel.ChannelType; public class ChannelState extends Module implements ICallEventProvider, IChangedAttributeProvider, - IDecoderStateEventListener, IDecoderStateEventProvider, IMetadataProvider, ISquelchStateProvider + IDecoderStateEventListener, IDecoderStateEventProvider, IMetadataProvider, ISquelchStateProvider, + IAttributeChangeRequestListener { private final static Logger mLog = LoggerFactory.getLogger( ChannelState.class ); public static final long FADE_TIMEOUT_DELAY = 1200; public static final long RESET_TIMEOUT_DELAY = 2000; - + + private MutableMetadata mMutableMetadata = new MutableMetadata(); private State mState = State.IDLE; private Listener mCallEventListener; @@ -107,7 +112,19 @@ public class ChannelState extends Module implements ICallEventProvider, IChanged { mChannelType = channelType; } - + + + /** + * Metadata for this channel containing channel details, primary and secondary entities for call events and any + * decoded messages. This metadata is primarily intended to be used by graphical components to convey the + * details of the current channel state and to provide metadata backing any decoded audio packets produced by + * any audio streams for this channel. + */ + public MutableMetadata getMutableMetadata() + { + return mMutableMetadata; + } + /** * Resets this channel state and prepares it for reuse. */ @@ -115,8 +132,9 @@ public class ChannelState extends Module implements ICallEventProvider, IChanged public void reset() { broadcast( new DecoderStateEvent( this, Event.RESET, State.IDLE ) ); - broadcast( new MetadataReset() ); - + + mMutableMetadata.resetTemporalAttributes(); + mState = State.IDLE; broadcast( Attribute.CHANNEL_STATE ); } @@ -126,6 +144,8 @@ public class ChannelState extends Module implements ICallEventProvider, IChanged { if( mStateMonitorFuture == null && mStateMonitor != null ) { + mMutableMetadata.resetAllAttributes(); + if( mTrafficChannelEndListener != null ) { setState( State.CALL ); @@ -419,7 +439,16 @@ public class ChannelState extends Module implements ICallEventProvider, IChanged mCallEventListener = null; } - /** + + + //NEW: + @Override + public Listener getAttributeChangeRequestListener() + { + return mMutableMetadata; + } + + /** * Broadcasts the channel state attribute change event to all registered * listeners */ @@ -439,7 +468,7 @@ public class ChannelState extends Module implements ICallEventProvider, IChanged { mChangedAttributeListener = listener; } - + /** * Removes the listener to receive channel state attribute change events */ diff --git a/src/channel/state/DecoderState.java b/src/channel/state/DecoderState.java index de5b04b9..8aaa20e2 100644 --- a/src/channel/state/DecoderState.java +++ b/src/channel/state/DecoderState.java @@ -1,6 +1,11 @@ package channel.state; +import alias.AliasList; +import audio.metadata.IMetadataProvider; +import audio.metadata.Metadata; import channel.metadata.Attribute; +import channel.metadata.AttributeChangeRequest; +import channel.metadata.IAttributeChangeRequestProvider; import message.IMessageListener; import message.Message; import module.Module; @@ -10,219 +15,182 @@ import module.decode.event.CallEvent; import module.decode.event.ICallEventProvider; import sample.Broadcaster; import sample.Listener; -import alias.AliasList; -import audio.metadata.IMetadataProvider; -import audio.metadata.Metadata; /** - * Channel state monitors the stream of decoded messages produced by the + * Channel state monitors the stream of decoded messages produced by the * decoder and broadcasts call events as they occur within the decoded message activity. - * + * * Provides access to a textual activity summary of events observed. */ -public abstract class DecoderState extends Module - implements ActivitySummaryProvider, - Listener, - ICallEventProvider, - IChangedAttributeProvider, - IDecoderStateEventListener, - IDecoderStateEventProvider, - IMessageListener, - IMetadataProvider +public abstract class DecoderState extends Module implements ActivitySummaryProvider, Listener, + IAttributeChangeRequestProvider, ICallEventProvider, IDecoderStateEventListener, + IDecoderStateEventProvider, IMessageListener { - /* This has to be a broadcaster in order for references to persist */ - private Broadcaster mCallEventBroadcaster = new Broadcaster<>(); - private Listener mChangedAttributeListener; - private Listener mDecoderStateListener; - private Listener mMetadataListener; - - private DecoderStateEventListener mDecoderStateEventListener = - new DecoderStateEventListener(); - - protected CallEvent mCurrentCallEvent; - - private AliasList mAliasList; - - public DecoderState( AliasList aliasList ) - { - mAliasList = aliasList; - } - - public abstract DecoderType getDecoderType(); - - public AliasList getAliasList() - { - return mAliasList; - } - - public boolean hasAliasList() - { - return mAliasList != null; - } - - /** - * Reset the decoder state to prepare for processing a different sample - * source - */ - public abstract void reset(); + /* This has to be a broadcaster in order for references to persist */ + private Broadcaster mCallEventBroadcaster = new Broadcaster<>(); + private Listener mAttributeChangeRequestListener; + private Listener mDecoderStateListener; - /** - * Allow the decoder to perform any setup actions - */ - public abstract void init(); + private DecoderStateEventListener mDecoderStateEventListener = new DecoderStateEventListener(); - /** - * Implements the IDecoderStateEventListener interface to receive state - * reset events. - */ - public abstract void receiveDecoderStateEvent( DecoderStateEvent event ); - - /** - * Disposes any resources or pointers held by this instance to prepare for - * garbage collection - */ - public void dispose() - { - mCallEventBroadcaster.dispose(); - mCallEventBroadcaster = null; - mChangedAttributeListener = null; - mDecoderStateListener = null; - mMetadataListener = null; + protected CallEvent mCurrentCallEvent; - mAliasList = null; - } - - /** - * Activity Summary - textual summary of activity observed by the channel state. - */ - public abstract String getActivitySummary(); + private AliasList mAliasList; - /** - * Broadcasts a call event to any registered listeners - */ - protected void broadcast( CallEvent event ) - { - mCallEventBroadcaster.broadcast( event ); - } + public DecoderState(AliasList aliasList) + { + mAliasList = aliasList; + } - /** - * Adds a call event listener - */ - @Override - public void addCallEventListener( Listener listener ) - { - mCallEventBroadcaster.addListener( listener ); - } + public abstract DecoderType getDecoderType(); - /** - * Removes the call event listener - */ - @Override - public void removeCallEventListener( Listener listener ) - { - mCallEventBroadcaster.removeListener( listener ); - } + public AliasList getAliasList() + { + return mAliasList; + } - @Override - public Listener getDecoderStateListener() - { - return mDecoderStateEventListener; - } - - private class DecoderStateEventListener implements Listener - { - @Override - public void receive( DecoderStateEvent event ) - { - receiveDecoderStateEvent( event ); - } - } - - /** - * Broadcasts the channel state attribute change event to all registered - * listeners - */ - protected void broadcast( Attribute attribute ) - { - if( mChangedAttributeListener != null ) - { - mChangedAttributeListener.receive( attribute ); - } - } + public boolean hasAliasList() + { + return mAliasList != null; + } - /** - * Adds the listener to receive channel state attribute change events - */ - @Override - public void setChangedAttributeListener( Listener listener ) - { - mChangedAttributeListener = listener; - } - - /** - * Removes the listener to receive channel state attribute change events - */ - @Override - public void removeChangedAttributeListener() - { - mChangedAttributeListener = null; - } + /** + * Reset the decoder state to prepare for processing a different sample + * source + */ + public abstract void reset(); - /** - * Broadcasts a channel state event to any registered listeners - */ - protected void broadcast( DecoderStateEvent event ) - { - if( mDecoderStateListener != null ) - { - mDecoderStateListener.receive( event ); - } - } + /** + * Allow the decoder to perform any setup actions + */ + public abstract void init(); - /** - * Adds a decoder state event listener - */ - @Override - public void setDecoderStateListener( Listener listener ) - { - mDecoderStateListener = listener; - } + /** + * Implements the IDecoderStateEventListener interface to receive state + * reset events. + */ + public abstract void receiveDecoderStateEvent(DecoderStateEvent event); - /** - * Removes the decoder state event listener - */ - @Override - public void removeDecoderStateListener() - { - mDecoderStateListener = null; - } + /** + * Disposes any resources or pointers held by this instance to prepare for + * garbage collection + */ + public void dispose() + { + mCallEventBroadcaster.dispose(); + mCallEventBroadcaster = null; + mDecoderStateListener = null; - /** - * Broadcasts metadata to a registered listener - */ - protected void broadcast( Metadata metadata ) - { - if( mMetadataListener != null ) - { - mMetadataListener.receive( metadata ); - } - } + mAliasList = null; + } - @Override - public void setMetadataListener( Listener listener ) - { - mMetadataListener = listener; - } + /** + * Activity Summary - textual summary of activity observed by the channel state. + */ + public abstract String getActivitySummary(); - @Override - public void removeMetadataListener() - { - mMetadataListener = null; - } + /** + * Broadcasts a call event to any registered listeners + */ + protected void broadcast(CallEvent event) + { + mCallEventBroadcaster.broadcast(event); + } - @Override - public Listener getMessageListener() - { - return this; - } + /** + * Adds a call event listener + */ + @Override + public void addCallEventListener(Listener listener) + { + mCallEventBroadcaster.addListener(listener); + } + + /** + * Removes the call event listener + */ + @Override + public void removeCallEventListener(Listener listener) + { + mCallEventBroadcaster.removeListener(listener); + } + + @Override + public Listener getDecoderStateListener() + { + return mDecoderStateEventListener; + } + + private class DecoderStateEventListener implements Listener + { + @Override + public void receive(DecoderStateEvent event) + { + receiveDecoderStateEvent(event); + } + } + + /** + * Broadcasts a channel state event to any registered listeners + */ + protected void broadcast(DecoderStateEvent event) + { + if(mDecoderStateListener != null) + { + mDecoderStateListener.receive(event); + } + } + + /** + * Adds a decoder state event listener + */ + @Override + public void setDecoderStateListener(Listener listener) + { + mDecoderStateListener = listener; + } + + /** + * Removes the decoder state event listener + */ + @Override + public void removeDecoderStateListener() + { + mDecoderStateListener = null; + } + + @Override + public Listener getMessageListener() + { + return this; + } + + /** + * Broadcasts the attribute change request to the registered listener + */ + protected void broadcast(AttributeChangeRequest request) + { + if(mAttributeChangeRequestListener != null) + { + mAttributeChangeRequestListener.receive(request); + } + } + + /** + * Sets the listener to receive attribute change requests from this decoder state + */ + @Override + public void setAttributeChangeRequestListener(Listener listener) + { + mAttributeChangeRequestListener = listener; + } + + /** + * Removes any listener from receiving attribute change requests + */ + @Override + public void removeAttributeChangeRequestListener() + { + mAttributeChangeRequestListener = null; + } } diff --git a/src/module/decode/fleetsync2/Fleetsync2DecoderState.java b/src/module/decode/fleetsync2/Fleetsync2DecoderState.java index 0f343109..ee9df66b 100644 --- a/src/module/decode/fleetsync2/Fleetsync2DecoderState.java +++ b/src/module/decode/fleetsync2/Fleetsync2DecoderState.java @@ -1,353 +1,354 @@ /******************************************************************************* * SDR Trunk * Copyright (C) 2014,2015 Dennis Sheirer - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see ******************************************************************************/ package module.decode.fleetsync2; -import java.util.Iterator; -import java.util.TreeSet; -import java.util.concurrent.ScheduledExecutorService; - -import message.Message; -import module.decode.DecoderType; +import alias.Alias; +import alias.AliasList; import channel.metadata.Attribute; import channel.state.DecoderState; import channel.state.DecoderStateEvent; import channel.state.DecoderStateEvent.Event; import channel.state.State; -import alias.Alias; -import alias.AliasList; +import message.Message; +import module.decode.DecoderType; + +import java.util.Iterator; +import java.util.TreeSet; +import java.util.concurrent.ScheduledExecutorService; public class Fleetsync2DecoderState extends DecoderState { - private TreeSet mIdents = new TreeSet(); - private TreeSet mEmergencyIdents = new TreeSet(); + private TreeSet mIdents = new TreeSet(); + private TreeSet mEmergencyIdents = new TreeSet(); - private String mFrom; - private Alias mFromAlias; + private String mFrom; + private Alias mFromAlias; - private String mTo; - private Alias mToAlias; + private String mTo; + private Alias mToAlias; - private String mMessage; - private String mMessageType; - - private long mFrequency; + private String mMessage; + private String mMessageType; - public Fleetsync2DecoderState( AliasList aliasList ) - { - super( aliasList ); - } - - @Override - public DecoderType getDecoderType() - { - return DecoderType.FLEETSYNC2; - } + private long mFrequency; - @Override - public void start( ScheduledExecutorService executor ) - { - } - - @Override - public void stop() - { - } - - @Override - public void dispose() - { - super.dispose(); - } - - @Override - public void init() - { - } - - /** - * Resets the overall decoder state and clears any accumulated event details - */ - @Override - public void reset() - { - mIdents.clear(); - mEmergencyIdents.clear(); - - resetState(); - } - - /** - * Resets this decoder state - */ - public void resetState() - { - setFromID( null ); - setFromIDAlias( null ); - setToID( null ); - setToIDAlias( null ); - setMessage( null ); - setMessageType( null ); - } - - @Override - public void receive( Message message ) + public Fleetsync2DecoderState(AliasList aliasList) { - if( message instanceof FleetsyncMessage ) - { - FleetsyncMessage fleetsync = (FleetsyncMessage)message; - - if( fleetsync.isValid() ) - { - State state = State.CALL; - - setFromID( fleetsync.getFromID() ); - setFromIDAlias( fleetsync.getFromIDAlias() ); - mIdents.add( fleetsync.getFromID() ); - - FleetsyncMessageType type = fleetsync.getMessageType(); + super(aliasList); + } - if( type != FleetsyncMessageType.ANI ) - { - setToID( fleetsync.getToID() ); - setToIDAlias( fleetsync.getToIDAlias() ); - mIdents.add( fleetsync.getToID() ); - } - - setMessageType( type.getLabel() ); + @Override + public DecoderType getDecoderType() + { + return DecoderType.FLEETSYNC2; + } - switch( type ) - { - case GPS: - setMessage( fleetsync.getGPSLocation() ); - state = State.DATA; - break; - case STATUS: - StringBuilder sb = new StringBuilder(); - sb.append( fleetsync.getStatus() ); - - Alias status = fleetsync.getStatusAlias(); - - if( status != null ) - { - sb.append( "/" ); - sb.append( status.getName() ); - } + @Override + public void start(ScheduledExecutorService executor) + { + } - setMessage( sb.toString() ); - state = State.DATA; - break; - case EMERGENCY: - case LONE_WORKER_EMERGENCY: - mEmergencyIdents.add( fleetsync.getFromID() ); - state = State.DATA; - break; - default: - break; - } + @Override + public void stop() + { + } - FleetsyncCallEvent fsCallEvent = - FleetsyncCallEvent.getFleetsync2Event( fleetsync, mFrequency ); + @Override + public void dispose() + { + super.dispose(); + } - fsCallEvent.setAliasList( getAliasList() ); + @Override + public void init() + { + } - broadcast( fsCallEvent ); + /** + * Resets the overall decoder state and clears any accumulated event details + */ + @Override + public void reset() + { + mIdents.clear(); + mEmergencyIdents.clear(); + + resetState(); + } + + /** + * Resets this decoder state + */ + public void resetState() + { + mFrom = null; + mFromAlias = null; + mTo = null; + mToAlias = null; + mMessage = null; + mMessageType = null; + } + + @Override + public void receive(Message message) + { + if(message instanceof FleetsyncMessage) + { + FleetsyncMessage fleetsync = (FleetsyncMessage) message; + + if(fleetsync.isValid()) + { + State state = State.CALL; + + setFromID(fleetsync.getFromID()); + setFromIDAlias(fleetsync.getFromIDAlias()); + mIdents.add(fleetsync.getFromID()); + + FleetsyncMessageType type = fleetsync.getMessageType(); + + if(type != FleetsyncMessageType.ANI) + { + setToID(fleetsync.getToID()); + setToIDAlias(fleetsync.getToIDAlias()); + mIdents.add(fleetsync.getToID()); + } + + setMessageType(type.getLabel()); + + switch(type) + { + case GPS: + setMessage(fleetsync.getGPSLocation()); + state = State.DATA; + break; + case STATUS: + StringBuilder sb = new StringBuilder(); + sb.append(fleetsync.getStatus()); + + Alias status = fleetsync.getStatusAlias(); + + if(status != null) + { + sb.append("/"); + sb.append(status.getName()); + } + + setMessage(sb.toString()); + state = State.DATA; + break; + case EMERGENCY: + case LONE_WORKER_EMERGENCY: + mEmergencyIdents.add(fleetsync.getFromID()); + state = State.DATA; + break; + default: + break; + } + + FleetsyncCallEvent fsCallEvent = + FleetsyncCallEvent.getFleetsync2Event(fleetsync, mFrequency); + + fsCallEvent.setAliasList(getAliasList()); + + broadcast(fsCallEvent); /* Broadcast decode event so that the channel state will - * kick in and reset everything after a short delay */ - broadcast( new DecoderStateEvent( this, Event.DECODE, state ) ); - } - } + * kick in and reset everything after a short delay */ + broadcast(new DecoderStateEvent(this, Event.DECODE, state)); + } + } } - - public String getFromID() - { - return mFrom; - } - - public void setFromID( String from ) - { - mFrom = from; - broadcast( Attribute.FROM_TALKGROUP ); - } - - public Alias getFromIDAlias() - { - return mFromAlias; - } - public void setFromIDAlias( Alias alias ) - { - mFromAlias = alias; - broadcast( Attribute.FROM_TALKGROUP_ALIAS ); - } - - public String getToID() - { - return mTo; - } - - public void setToID( String to ) - { - mTo = to; - broadcast( Attribute.TO_TALKGROUP ); - } - - public Alias getToIDAlias() - { - return mToAlias; - } - - public void setToIDAlias( Alias alias ) - { - mToAlias = alias; - broadcast( Attribute.TO_TALKGROUP_ALIAS ); - } - - public String getMessage() - { - return mMessage; - } + public String getFromID() + { + return mFrom; + } - public void setMessage( String message ) - { - mMessage = message; - broadcast( Attribute.MESSAGE ); - } - - public String getMessageType() - { - return mMessageType; - } + public void setFromID(String from) + { + mFrom = from; - public void setMessageType( String type ) - { - mMessageType = type; - broadcast( Attribute.MESSAGE_TYPE ); - } - - /** - * Responds to reset events issued by the channel state - */ - @Override - public void receiveDecoderStateEvent( DecoderStateEvent event ) - { - switch( event.getEvent() ) - { - case RESET: - resetState(); - break; - case SOURCE_FREQUENCY: - mFrequency = event.getFrequency(); - break; - default: - break; - } - } - - @Override + broadcast(Attribute.FROM_TALKGROUP); + } + + public Alias getFromIDAlias() + { + return mFromAlias; + } + + public void setFromIDAlias(Alias alias) + { + mFromAlias = alias; + broadcast(Attribute.FROM_TALKGROUP_ALIAS); + } + + public String getToID() + { + return mTo; + } + + public void setToID(String to) + { + mTo = to; + broadcast(Attribute.TO_TALKGROUP); + } + + public Alias getToIDAlias() + { + return mToAlias; + } + + public void setToIDAlias(Alias alias) + { + mToAlias = alias; + broadcast(Attribute.TO_TALKGROUP_ALIAS); + } + + public String getMessage() + { + return mMessage; + } + + public void setMessage(String message) + { + mMessage = message; + broadcast(Attribute.MESSAGE); + } + + public String getMessageType() + { + return mMessageType; + } + + public void setMessageType(String type) + { + mMessageType = type; + broadcast(Attribute.MESSAGE_TYPE); + } + + /** + * Responds to reset events issued by the channel state + */ + @Override + public void receiveDecoderStateEvent(DecoderStateEvent event) + { + switch(event.getEvent()) + { + case RESET: + resetState(); + break; + case SOURCE_FREQUENCY: + mFrequency = event.getFrequency(); + break; + default: + break; + } + } + + @Override public String getActivitySummary() { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(); - sb.append( "=============================\n" ); - sb.append( "Decoder:\tFleetsync II\n\n" ); - sb.append( "Fleetsync Idents\n" ); + sb.append("=============================\n"); + sb.append("Decoder:\tFleetsync II\n\n"); + sb.append("Fleetsync Idents\n"); - if( mIdents.isEmpty() ) - { - sb.append( " None\n" ); - } - else - { - Iterator it = mIdents.iterator(); - - while( it.hasNext() ) - { - String ident = it.next(); - - sb.append( " " ); - sb.append( formatIdent( ident ) ); + if(mIdents.isEmpty()) + { + sb.append(" None\n"); + } + else + { + Iterator it = mIdents.iterator(); - if( hasAliasList() ) - { - Alias alias = getAliasList().getFleetsyncAlias( ident ); - - if( alias != null ) - { - sb.append( " " ); - sb.append( alias.getName() ); - } - } - - sb.append( "\n" ); - } - } - - sb.append( "\nFleetsync Emergency Activations\n" ); + while(it.hasNext()) + { + String ident = it.next(); - if( mEmergencyIdents.isEmpty() ) - { - sb.append( " None\n" ); - } - else - { - Iterator it = mEmergencyIdents.iterator(); - - while( it.hasNext() ) - { - String ident = it.next(); - - sb.append( " " ); - sb.append( formatIdent( ident ) ); + sb.append(" "); + sb.append(formatIdent(ident)); - if( hasAliasList() ) - { - Alias alias = getAliasList().getFleetsyncAlias( ident ); - - if( alias != null ) - { - sb.append( " " ); - sb.append( alias.getName() ); - } - - sb.append( "\n" ); - } - } - } + if(hasAliasList()) + { + Alias alias = getAliasList().getFleetsyncAlias(ident); - return sb.toString(); + if(alias != null) + { + sb.append(" "); + sb.append(alias.getName()); + } + } + + sb.append("\n"); + } + } + + sb.append("\nFleetsync Emergency Activations\n"); + + if(mEmergencyIdents.isEmpty()) + { + sb.append(" None\n"); + } + else + { + Iterator it = mEmergencyIdents.iterator(); + + while(it.hasNext()) + { + String ident = it.next(); + + sb.append(" "); + sb.append(formatIdent(ident)); + + if(hasAliasList()) + { + Alias alias = getAliasList().getFleetsyncAlias(ident); + + if(alias != null) + { + sb.append(" "); + sb.append(alias.getName()); + } + + sb.append("\n"); + } + } + } + + return sb.toString(); } - public static String formatIdent( String ident ) - { - StringBuilder sb = new StringBuilder(); - - if( ident.length() == 7 ) - { - sb.append( ident.substring( 0, 3 ) ); - sb.append( "-" ); - sb.append( ident.substring( 3, 7 ) ); + public static String formatIdent(String ident) + { + StringBuilder sb = new StringBuilder(); - return sb.toString(); - } - else - { - return ident; - } - } + if(ident.length() == 7) + { + sb.append(ident.substring(0, 3)); + sb.append("-"); + sb.append(ident.substring(3, 7)); + + return sb.toString(); + } + else + { + return ident; + } + } }