#766 Resolves issue with playlist alias editor clone alias feature causing multiple copies of alias. Changed clone feature to only use a shallow copy (no ids or actions). Removed alias event framework from code since all change notifications can now be detected by the alias model's aliasList property. (#791)

Co-authored-by: Denny <denny@denny-desktop>
This commit is contained in:
Denny Sheirer
2020-05-04 10:20:49 +00:00
committed by GitHub
parent 08ea0e0f18
commit 5d580544e7
10 changed files with 248 additions and 442 deletions

View File

@@ -1,55 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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 <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.alias;
public class AliasEvent
{
private Alias mAlias;
private Event mEvent;
/**
* AliasEvent - event describing any changes to an alias
* @param alias - alias that changed
* @param event - change event
*/
public AliasEvent( Alias alias, Event event )
{
mAlias = alias;
mEvent = event;
}
public Alias getAlias()
{
return mAlias;
}
public Event getEvent()
{
return mEvent;
}
/**
* Channel events to describe the specific event
*/
public enum Event
{
ADD,
CHANGE,
DELETE;
}
}

View File

@@ -51,6 +51,9 @@ import io.github.dsheirer.alias.id.tone.TonesID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
public class AliasFactory
{
private static final Logger mLog = LoggerFactory.getLogger(AliasFactory.class);
@@ -180,32 +183,56 @@ public class AliasFactory
return null;
}
public static Alias copyOf(Alias original)
public static Alias shallowCopyOf(Alias original)
{
Alias copy = new Alias(original.getName());
copy.setAliasListName(original.getAliasListName());
copy.setGroup(original.getGroup());
copy.setColor(original.getColor());
copy.setIconName(original.getIconName());
return copy;
}
public static List<AliasID> copyAliasIDs(Alias original)
{
List<AliasID> aliasIDS = new ArrayList<>();
for(AliasID id : original.getAliasIdentifiers())
{
AliasID copyID = copyOf(id);
if(copyID != null)
{
copy.addAliasID(copyID);
}
aliasIDS.add(copyOf(id));
}
for(AliasAction action : original.getAliasActions())
{
AliasAction copyAction = copyOf(action);
return aliasIDS;
}
if(copyAction != null)
{
copy.addAliasAction(copyAction);
}
public static List<AliasAction> copyAliasActions(Alias original)
{
List<AliasAction> actions = new ArrayList<>();
for(AliasAction aliasAction: original.getAliasActions())
{
actions.add(copyOf(aliasAction));
}
return actions;
}
public static Alias copyOf(Alias original)
{
Alias copy = shallowCopyOf(original);
List<AliasID> aliasIDS = copyAliasIDs(original);
for(AliasID aliasID: aliasIDS)
{
copy.addAliasID(aliasID);
}
List<AliasAction> actions = copyAliasActions(original);
for(AliasAction action: actions)
{
copy.addAliasAction(action);
}
return copy;

View File

@@ -45,19 +45,27 @@ import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.identifier.tone.ToneIdentifier;
import io.github.dsheirer.identifier.tone.ToneSequence;
import io.github.dsheirer.protocol.Protocol;
import io.github.dsheirer.sample.Listener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
* List of aliases that share the same alias list name and provides convenient methods for looking up alias
* objects that match an identifier.
*/
public class AliasList implements Listener<AliasEvent>
public class AliasList
{
private final static Logger mLog = LoggerFactory.getLogger(AliasList.class);
private Map<Protocol,TalkgroupAliasList> mTalkgroupProtocolMap = new EnumMap<>(Protocol.class);
@@ -96,10 +104,7 @@ public class AliasList implements Listener<AliasEvent>
{
if(alias != null)
{
for(AliasID aliasID : alias.getAliasIdentifiers())
{
addAliasID(aliasID, alias);
}
alias.getAliasIdentifiers().stream().forEach(aliasID -> addAliasID(aliasID, alias));
}
if(alias.hasActions())
@@ -107,7 +112,10 @@ public class AliasList implements Listener<AliasEvent>
mHasAliasActions = true;
}
mAliases.add(alias);
if(!mAliases.contains(alias))
{
mAliases.add(alias);
}
}
/**
@@ -174,7 +182,7 @@ public class AliasList implements Listener<AliasEvent>
radioRangeAliasList.add(radioRange, alias);
break;
case ESN:
String esn = ((Esn) id).getEsn();
String esn = ((Esn)id).getEsn();
if(esn != null && !esn.isEmpty())
{
@@ -184,7 +192,7 @@ public class AliasList implements Listener<AliasEvent>
case STATUS:
int userStatus = ((UserStatusID)id).getStatus();
if(mUserStatusMap.containsKey(userStatus))
if(mUserStatusMap.containsKey(userStatus) && !mUserStatusMap.get(userStatus).equals(alias))
{
id.setOverlap(true);
@@ -203,7 +211,7 @@ public class AliasList implements Listener<AliasEvent>
case UNIT_STATUS:
int unitStatus = ((UnitStatusID)id).getStatus();
if(mUnitStatusMap.containsKey(unitStatus))
if(mUnitStatusMap.containsKey(unitStatus) && !mUnitStatusMap.get(unitStatus).equals(alias))
{
id.setOverlap(true);
@@ -224,7 +232,7 @@ public class AliasList implements Listener<AliasEvent>
if(toneSequence != null)
{
if(mToneSequenceMap.containsKey(toneSequence))
if(mToneSequenceMap.containsKey(toneSequence) && !mToneSequenceMap.get(toneSequence).equals(alias))
{
id.setOverlap(true);
@@ -232,7 +240,7 @@ public class AliasList implements Listener<AliasEvent>
for(AliasID aliasID: existing.getAliasIdentifiers())
{
if(aliasID instanceof TonesID && ((TonesID)aliasID).equals(id))
if(aliasID instanceof TonesID && aliasID.equals(id))
{
aliasID.setOverlap(true);
}
@@ -253,112 +261,50 @@ public class AliasList implements Listener<AliasEvent>
}
}
/**
* Removes the alias and alias identifier to the internal type mapping.
*/
private void removeAliasID(AliasID id, Alias alias)
{
try
{
switch(id.getType())
{
case TALKGROUP:
Talkgroup talkgroup = (Talkgroup)id;
TalkgroupAliasList talkgroupAliasList = mTalkgroupProtocolMap.get(talkgroup.getProtocol());
if(talkgroupAliasList != null)
{
talkgroupAliasList.remove(talkgroup, alias);
}
break;
case TALKGROUP_RANGE:
TalkgroupRange talkgroupRange = (TalkgroupRange)id;
TalkgroupAliasList talkgroupRangeAliasList = mTalkgroupProtocolMap.get(talkgroupRange.getProtocol());
if(talkgroupRangeAliasList != null)
{
talkgroupRangeAliasList.remove(talkgroupRange, alias);
}
break;
case RADIO_ID:
Radio radio = (Radio)id;
RadioAliasList radioAliasList = mRadioProtocolMap.get(radio.getProtocol());
if(radioAliasList != null)
{
radioAliasList.remove(radio, alias);
}
break;
case RADIO_ID_RANGE:
RadioRange radioRange = (RadioRange)id;
RadioAliasList radioRangeAliasList = mRadioProtocolMap.get(radioRange.getProtocol());
if(radioRangeAliasList != null)
{
radioRangeAliasList.remove(radioRange, alias);
}
break;
case ESN:
String esn = ((Esn) id).getEsn();
if(esn != null && !esn.isEmpty())
{
String key = esn.toLowerCase();
if(mESNMap.containsKey(key) && mESNMap.get(key) == alias)
{
mESNMap.remove(key);
}
}
break;
case STATUS:
int status = ((UserStatusID)id).getStatus();
if(mUserStatusMap.containsKey(status) && mUserStatusMap.get(status) == alias)
{
mUserStatusMap.remove(status);
}
break;
case UNIT_STATUS:
int unitStatus = ((UnitStatusID)id).getStatus();
if(mUnitStatusMap.containsKey(unitStatus) && mUnitStatusMap.get(unitStatus) == alias)
{
mUnitStatusMap.remove(unitStatus);
}
break;
case TONES:
ToneSequence toneSequence = ((TonesID)id).getToneSequence();
if(toneSequence != null && mToneSequenceMap.containsKey(toneSequence) &&
mToneSequenceMap.get(toneSequence) == alias)
{
mToneSequenceMap.remove(toneSequence);
}
break;
}
}
catch(Exception e)
{
mLog.error("Couldn't remove alias ID " + id + " for alias " + alias, e);
}
}
/**
* Removes the alias from this list
*/
public void removeAlias(Alias alias)
{
for(AliasID aliasID: alias.getAliasIdentifiers())
//Note: because the alias' identifiers could have changed from when we initially added the alias, we have to
//inspect every collection and map to remove the alias completely.
mAliases.remove(alias);
mTalkgroupProtocolMap.values().stream().forEach(talkgroupAliasList -> talkgroupAliasList.remove(alias));
mRadioProtocolMap.values().stream().forEach(radioAliasList -> radioAliasList.remove(alias));
Collection<Alias> collection = Collections.singleton(alias);
mESNMap.values().removeAll(collection);
mUnitStatusMap.values().removeAll(collection);
mUserStatusMap.values().removeAll(collection);
mToneSequenceMap.values().removeAll(collection);
validate();
}
/**
* Identifies all aliases with an alias identifier that has the overlap flag set, resets the flag, and then readds
* each alias back to this alias list so that overlap can be detected again.
*/
public void validate()
{
Set<Alias> overlapAliases = new HashSet<>();
List<Alias> aliases = new ArrayList<>(mAliases);
for(Alias alias: aliases)
{
removeAliasID(aliasID, alias);
for(AliasID aliasID: alias.getAliasIdentifiers())
{
if(aliasID.overlapProperty().get())
{
aliasID.setOverlap(false);
overlapAliases.add(alias);
}
}
}
mAliases.remove(alias);
overlapAliases.stream().forEach(alias -> addAlias(alias));
}
/**
@@ -402,32 +348,15 @@ public class AliasList implements Listener<AliasEvent>
}
/**
* Receive alias change event notifications and modify this list accordingly
* Updates the alias by removing it from this list and then adding it back to this list when the list name matches.
*/
@Override
public void receive(AliasEvent event)
public void updateAlias(Alias alias)
{
if(hasName())
{
Alias alias = event.getAlias();
removeAlias(alias);
switch(event.getEvent())
{
case ADD:
if(alias.getAliasListName() != null && getName().equalsIgnoreCase(alias.getAliasListName()))
{
addAlias(alias);
}
break;
case DELETE:
if(alias.getAliasListName() != null && getName().equalsIgnoreCase(alias.getAliasListName()))
{
removeAlias(alias);
}
break;
default:
break;
}
if(hasName() && getName().equals(alias.getAliasListName()))
{
addAlias(alias);
}
}
@@ -659,34 +588,6 @@ public class AliasList implements Listener<AliasEvent>
return channels;
}
/**
* Identifies all aliases that have alias identifiers that match the alias ID and are in the same alias list.
* @param alias for the list to search
* @param aliasID to find matches
* @return list of one or more aliases that have matching alias identifiers
*/
public List<Alias> getOverlappingAliases(Alias alias, AliasID aliasID)
{
List<Alias> aliases = new ArrayList<>();
for(Alias otherAlias: mAliases)
{
if(otherAlias != alias)
{
for(AliasID otherId: otherAlias.getAliasIdentifiers())
{
if(otherId.matches(aliasID) || otherId.overlaps(aliasID))
{
aliases.add(otherAlias);
continue;
}
}
}
}
return aliases;
}
/**
* Listing of talkgroups and ranges for a specific protocol
*/
@@ -759,21 +660,13 @@ public class AliasList implements Listener<AliasEvent>
mTalkgroupRangeAliasMap.put(talkgroupRange, alias);
}
public void remove(Talkgroup talkgroup, Alias alias)
/**
* Removes the alias from both the talkgroup and the talkgroup range maps.
*/
public void remove(Alias alias)
{
if(mTalkgroupAliasMap.containsKey(talkgroup.getValue()) && mTalkgroupAliasMap.get(talkgroup.getValue()) == alias)
{
mTalkgroupAliasMap.remove(talkgroup.getValue());
}
}
public void remove(TalkgroupRange talkgroupRange, Alias alias)
{
//Only remove the entry if both the key and the value match
if(mTalkgroupRangeAliasMap.containsKey(talkgroupRange) && mTalkgroupRangeAliasMap.get(talkgroupRange) == alias)
{
mTalkgroupRangeAliasMap.remove(talkgroupRange);
}
mTalkgroupAliasMap.values().removeAll(Collections.singleton(alias));
mTalkgroupRangeAliasMap.values().removeAll(Collections.singleton(alias));
}
}
@@ -849,22 +742,13 @@ public class AliasList implements Listener<AliasEvent>
mRadioRangeAliasMap.put(radioRange, alias);
}
public void remove(Radio radio, Alias alias)
/**
* Removes the alias from both the radio and the radio range maps.
*/
public void remove(Alias alias)
{
//Only remove the entry if both the key and the value match
if(mRadioAliasMap.containsKey(radio.getValue()) && mRadioAliasMap.get(radio.getValue()) == alias)
{
mRadioAliasMap.remove(radio.getValue());
}
}
public void remove(RadioRange radioRange, Alias alias)
{
//Only remove the entry if both the key and the value match
if(mRadioRangeAliasMap.containsKey(radioRange) && mRadioRangeAliasMap.get(radioRange) == alias)
{
mRadioRangeAliasMap.remove(radioRange);
}
mRadioAliasMap.values().removeAll(Collections.singleton(alias));
mRadioRangeAliasMap.values().removeAll(Collections.singleton(alias));
}
}
}

View File

@@ -26,8 +26,6 @@ import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
@@ -51,7 +49,6 @@ public class AliasModel
public static final String NO_ALIAS_LIST = "(No Alias List)";
private ObservableList<Alias> mAliases = FXCollections.observableArrayList(Alias.extractor());
private ObservableList<String> mAliasListNames = FXCollections.observableArrayList();
private Broadcaster<AliasEvent> mAliasEventBroadcaster = new Broadcaster<>();
private Map<String,AliasList> mAliasListMap = new HashMap<>();
public AliasModel()
@@ -135,7 +132,8 @@ public class AliasModel
}
AliasList mapValue = mAliasListMap.get(name);
if (mapValue != null) {
if (mapValue != null)
{
return mapValue;
}
@@ -151,9 +149,6 @@ public class AliasModel
mAliasListMap.put(name, aliasList);
//Register the new alias list to receive updates from this model
addListener(aliasList);
return aliasList;
}
@@ -199,18 +194,19 @@ public class AliasModel
/**
* Adds the alias to the model
*/
public int addAlias(Alias alias)
public void addAlias(Alias alias)
{
if(alias != null)
if(mAliases.contains(alias))
{
mAliases.add(alias);
addAliasList(alias.getAliasListName());
int index = mAliases.size() - 1;
broadcast(new AliasEvent(alias, AliasEvent.Event.ADD));
return index;
removeAlias(alias);
}
return -1;
mAliases.add(alias);
}
private boolean hasAliasList(String aliasListName)
{
return aliasListName != null && mAliasListMap.containsKey(aliasListName);
}
public void addAliasList(String aliasListName)
@@ -220,7 +216,6 @@ public class AliasModel
if(!mAliasListNames.contains(aliasListName))
{
mAliasListNames.add(aliasListName);
FXCollections.sort(mAliasListNames);
}
}
else if(!mAliasListNames.contains(NO_ALIAS_LIST))
@@ -232,14 +227,18 @@ public class AliasModel
}
/**
* Removes the channel from the model and broadcasts a channel remove event
* Removes the alias from this model and an alias list (if one exists)
*/
public void removeAlias(Alias alias)
{
if(alias != null)
{
mAliases.remove(alias);
broadcast(new AliasEvent(alias, AliasEvent.Event.DELETE));
if(hasAliasList(alias.getAliasListName()))
{
getAliasList(alias.getAliasListName()).removeAlias(alias);
}
}
}
@@ -328,32 +327,8 @@ public class AliasModel
}
}
public void broadcast(AliasEvent event)
{
Alias alias = event.getAlias();
//Validate the alias following a user action that changed the alias or any alias IDs
if(alias != null)
{
alias.validate();
}
mAliasEventBroadcaster.broadcast(event);
}
public void addListener(Listener<AliasEvent> listener)
{
mAliasEventBroadcaster.addListener(listener);
}
public void removeListener(Listener<AliasEvent> listener)
{
mAliasEventBroadcaster.removeListener(listener);
}
/**
* Observable list change listener for both channels and traffic channels lists
* Monitors alias additions and removals and updates alias lists
*/
public class AliasListChangeListener implements ListChangeListener<Alias>
{
@@ -366,21 +341,27 @@ public class AliasModel
{
for(Alias alias: change.getAddedSubList())
{
mAliasEventBroadcaster.broadcast(new AliasEvent(alias, AliasEvent.Event.ADD));
addAliasList(alias.getAliasListName());
if(hasAliasList(alias.getAliasListName()))
{
getAliasList(alias.getAliasListName()).addAlias(alias);
}
}
}
else if(change.wasRemoved())
{
for(Alias alias: change.getRemoved())
{
mAliasEventBroadcaster.broadcast(new AliasEvent(alias, AliasEvent.Event.DELETE));
}
}
else if(change.wasUpdated())
{
for(int x = change.getFrom(); x < change.getTo(); x++)
{
mAliasEventBroadcaster.broadcast(new AliasEvent(change.getList().get(x), AliasEvent.Event.CHANGE));
if(hasAliasList(alias.getAliasListName()))
{
AliasList aliasList = getAliasList(alias.getName());
if(alias != null)
{
aliasList.removeAlias(alias);
}
}
}
}
}

View File

@@ -1,32 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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 <http://www.gnu.org/licenses/>
******************************************************************************/
package io.github.dsheirer.alias.action;
import io.github.dsheirer.alias.AliasEvent;
/**
* Interface for receiving alias events. Each listener will receive
* notification of all alias events.
*/
public interface AliasActionListener
{
/**
* Notifies the listener that an alias event has occurred
*/
public void aliasChanged( AliasEvent event );
}