/* StyledTagSet.java created 2007-09-28
*
*/
package org.signalml.domain.tag;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.swing.KeyStroke;
import javax.swing.event.EventListenerList;
import org.apache.log4j.Logger;
import org.signalml.app.config.preset.Preset;
import org.signalml.app.model.document.opensignal.elements.SignalParameters;
import org.signalml.domain.montage.Montage;
import org.signalml.exception.SanityCheckException;
import org.signalml.plugin.export.signal.SignalSelection;
import org.signalml.plugin.export.signal.SignalSelectionType;
import org.signalml.plugin.export.signal.Tag;
import org.signalml.plugin.export.signal.TagStyle;
import org.signalml.plugin.export.signal.tagStyle.TagAttributeValue;
import org.signalml.plugin.export.signal.tagStyle.TagStyleAttributeDefinition;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamConverter;
/**
* This class represents a set of
* {@link Tag tagged selections} and their {@link TagStyles styles}.
* Two tagged selections with the same type can not intersect so this class
* splits, merges and replaces them while adding.
*
* This class contains additional information such as a size of a page, a number
* of blocks per page and a length of a block (in seconds).
*
* @author Michal Dobaczewski © 2007-2008 CC Otwarte Systemy Komputerowe Sp. z o.o.
*/
@XStreamAlias("tagFile")
@XStreamConverter(StyledTagSetConverter.class)
public class StyledTagSet implements Serializable, Preset {
private static final long serialVersionUID = 1L;
protected static final Logger logger = Logger.getLogger(StyledTagSet.class);
/**
* page size in seconds
*/
private float pageSize;
/**
* the number of blocks in a page
*/
private int blocksPerPage;
/**
* block size in seconds
*/
private float blockSize;
private TagSignalIdentification tagSignalIdentification;
/**
* Contains the lists of all tag styles used in this styled tag set.
*/
protected TagStyles styles;
/**
* Collection of all tagged selections.
*/
protected TreeSet<Tag> tags;
/**
* Maximal length of a tagged selection in <i>tags</i>.
* This is just an estimate - may be 10% more than the actual length
* of the longest tag in the set.
*/
protected double maxTagLength = 0;
/**
* list of tagged selections of signal pages
*/
private ArrayList<Tag> pageTagsCache = null;
private ArrayList<Tag> blockTagsCache = null;
private ArrayList<Tag> channelTagsCache = null;
/**
* the description of the tagged set
*/
private String info;
/**
* the description of the montage
*/
private String montageInfo;
/**
* the tagged montage
*/
private Montage montage;
/**
* list of listeners associated with the current object
*/
private EventListenerList listenerList = new EventListenerList();
/**
* StyledTagSet preset name.
*/
private String name;
/**
* Constructor. Creates a default StyledTagSet without any tags or
* styles.
*/
public StyledTagSet() {
this(null, null, SignalParameters.DEFAULT_PAGE_SIZE, SignalParameters.DEFAULT_BLOCKS_PER_PAGE);
}
/**
* Constructor. Creates a StyledTagSet with given size of a page and
* given number of blocks per page, but without any tags or styles.
* @param pageSize a size of a page in seconds
* @param blocksPerPage a number of blocks per page
*/
public StyledTagSet(float pageSize, int blocksPerPage) {
this(null, null, pageSize, blocksPerPage);
}
/**
* Constructor. Creates a StyledTagSet with given styles of selections,
* but without tagged selections.
* Default sizes of a page and a block are used.
* @param styles {@link TagStyle tag styles} to be added to the created object
*/
public StyledTagSet(TagStyles styles) {
this(styles, null, SignalParameters.DEFAULT_PAGE_SIZE, SignalParameters.DEFAULT_BLOCKS_PER_PAGE);
}
/**
* Constructor. Creates a StyledTagSet with given styles of selections,
* size of a page and number of blocks per page, but without
* tagged selections.
* @param styles {@link TagStyle tag styles} to be added to the created object
* @param pageSize a size of a page in seconds
* @param blocksPerPage a number of blocks per page
*/
public StyledTagSet(TagStyles styles, float pageSize, int blocksPerPage) {
this(styles, null, pageSize, blocksPerPage);
}
/**
* Constructor. Creates a StyledTagSet with given styles of selections
* and given tagged selections.
* Default sizes of a page and a block are used.
* @param styles {@link TagStyle tag styles} to be added to the created object
* @param tags tagged selections to be added to the created object
*/
public StyledTagSet(TagStyles styles, TreeSet<Tag> tags) {
this(styles, tags, SignalParameters.DEFAULT_PAGE_SIZE, SignalParameters.DEFAULT_BLOCKS_PER_PAGE);
}
/**
* Constructor. Creates a StyledTagSet with given parameters.
* @param {@link TagStyle tag styles} to be added to the created object
* @param tags tagged selections to be added to the created object
* @param pageSize a size of a page in seconds
* @param blocksPerPage a number of blocks per page
*/
public StyledTagSet(TagStyles styles, TreeSet<Tag> tags, float pageSize, int blocksPerPage) {
if (pageSize <= 0) {
throw new SanityCheckException("Page size must be > 0");
}
if (blocksPerPage <= 0) {
throw new SanityCheckException("Blocks per page must be > 0"); // free-style block tags disabled for now
}
this.styles = styles != null? styles: new TagStyles();
this.styles.setStyledTagSet(this);
if (tags == null) {
this.tags = new TreeSet<Tag>();
} else {
this.tags = tags;
}
this.pageSize = pageSize;
this.blocksPerPage = blocksPerPage;
if (blocksPerPage > 0) {
blockSize = pageSize / blocksPerPage;
} else {
blockSize = -1;
}
if (!verifyTags()) {
throw new SanityCheckException("Tags not compatible with settings");
}
calculateMaxTagLength();
}
/**
* Returns a size of a page in seconds.
* @return a size of a page in seconds
*/
public float getPageSize() {
return pageSize;
}
/**
* Returns a number of blocks per page.
* @return a number of blocks per page
*/
public int getBlocksPerPage() {
return blocksPerPage;
}
/**
* Returns a size of a block in seconds.
* @return a size of a block in seconds
*/
public float getBlockSize() {
return blockSize;
}
/**
* Returns the {@link Montage montage} tagged by this set.
* @return the montage tagged by this set
*/
public Montage getMontage() {
return montage;
}
/**
* Sets the {@link Montage montage} tagged by this set.
* @param montage the montage tagged by this set
*/
public void setMontage(Montage montage) {
this.montage = montage;
}
/**
* Returns the list of {@link TagStyle styles} of tagged selections.
* @return the lists of all tag styles
*/
public List<TagStyle> getListOfStyles() {
return styles.getAllStyles();
}
/**
* Returns the object containing all tag styles contained in this
* {@link StyledTagSet}.
* @return the tag styles object connected with this StylesTagSet
*/
public TagStyles getTagStyles() {
return styles;
}
/**
* Returns the list of {@link TagStyle styles} of
* {@link Tag tagged selections} for a given
* {@link SignalSelectionType type} of a selection.
* @param type the type of a selection
* @return the list of styles of tagged selections for a given type of
* a selection
*/
public List<TagStyle> getStyles(SignalSelectionType type) {
return styles.getStyles(type);
}
/**
* Returns the list of {@link TagStyle styles} of
* {@link Tag tagged selections} for a given
* {@link SignalSelectionType type} of a selection, excluding markers
* if needed.
* @param type the type of a selection
* @param allowMarkers false if markers should be excluded,
* true otherwise
* @return the list of styles
*/
public List<TagStyle> getStyles(SignalSelectionType type, boolean allowMarkers) {
return styles.getStyles(type, allowMarkers);
}
/**
* Returns the list of {@link TagStyle styles} of tagged page selections.
* @return the list of styles of tagged page selections
*/
public List<TagStyle> getPageStyles() {
return styles.getStyles(SignalSelectionType.PAGE);
}
/**
* Returns the list of {@link TagStyle styles} of tagged block selections.
* @return the list of styles of tagged block selections
*/
public List<TagStyle> getBlockStyles() {
return styles.getStyles(SignalSelectionType.BLOCK);
}
/**
* Returns the list of {@link TagStyle styles} of tagged channel
* selections.
* @return the list of styles of tagged channel selections
*/
public List<TagStyle> getChannelStyles() {
return styles.getStyles(SignalSelectionType.CHANNEL);
}
/**
* Returns the list of {@link TagStyle styles} of tagged page selections
* excluding markers.
* @return the list of styles of tagged page selections without markers
*/
public List<TagStyle> getPageStylesNoMarkers() {
return styles.getStyles(SignalSelectionType.PAGE, false);
}
/**
* Returns the list of {@link TagStyle styles} of tagged block selections
* excluding markers.
* @return the list of styles of tagged block selections without markers
*/
public List<TagStyle> getBlockStylesNoMarkers() {
return styles.getStyles(SignalSelectionType.BLOCK, false);
}
/**
* Returns the list of {@link TagStyle styles} of tagged channel
* selections excluding markers.
* @return the list of styles of tagged channel selections without markers
*/
public List<TagStyle> getChannelStylesNoMarkers() {
return styles.getStyles(SignalSelectionType.CHANNEL, false);
}
/**
* Returns the {@link TagStyle style} of a given name
* with the given type.
* @param type the type of the style (page/block/channel)
* @param name the name of a style
* @return the style of a given name
*/
public TagStyle getStyle(SignalSelectionType type, String name) {
return styles.getStyle(type, name);
}
/**
* Returns the number of {@link TagStyle styles}.
* @return the number of styles
*/
public int getTagStyleCount() {
return styles.getStylesCount();
}
/**
* Returns the number of {@link TagStyle styles} for a given type of
* a selection.
* @param type the type of a selection
* @return the number of styles for a given type of a selection
*/
public int getTagStyleCount(SignalSelectionType type) {
return styles.getStylesCount(type);
}
/**
* Returns the number of {@link TagStyle styles} for page selections.
* @return the number of styles for page selections
*/
public int getPageStyleCount() {
return getTagStyleCount(SignalSelectionType.PAGE);
}
/**
* Returns the number of {@link TagStyle styles} for block selections.
* @return the number of styles for block selections
*/
public int getBlockStyleCount() {
return getTagStyleCount(SignalSelectionType.BLOCK);
}
/**
* Returns the number of {@link TagStyle styles} for channel selections.
* @return the number of styles for channel selections
*/
public int getChannelStyleCount() {
return getTagStyleCount(SignalSelectionType.CHANNEL);
}
/**
* Returns the style of a given index in an array of
* {@link TagStyle styles} for a given type of a selection.
* @param type the type of a selection
* @param index the index in an array of styles for a given
* type of a selection
* @return the found style
*/
public TagStyle getStyleAt(SignalSelectionType type, int index) {
return styles.getStyleAt(type, index);
}
/**
* Returns the {@link TagStyle style} of a given index in an array of
* page styles.
* @param index the index in an array of page styles
* @return the found style
*/
public TagStyle getPageStyleAt(int index) {
return styles.getStyleAt(SignalSelectionType.PAGE, index);
}
/**
* Returns the {@link TagStyle style} of a given index in an array
* of block styles.
* @param index the index in an array of block styles
* @return the found style
*/
public TagStyle getBlockStyleAt(int index) {
return styles.getStyleAt(SignalSelectionType.BLOCK, index);
}
/**
* Returns the {@link TagStyle style} of a given index in an array
* of page styles.
* @param index the index in an array of block styles
* @return the found style
*/
public TagStyle getChannelStyleAt(int index) {
return styles.getStyleAt(SignalSelectionType.CHANNEL, index);
}
/**
* Returns an index of a {@link TagStyle styles} in an appropriate array.
* @param style the style which index will be checked
* @return an index of a style in an appropriate array, -1 if style is
* not in any array
*/
public int indexOfStyle(TagStyle style) {
return styles.getIndexOf(style);
}
/**
* Returns an index of a {@link TagStyle style} in an array of
* page styles.
* @param style the style which index will be checked
* @return an index of a style in an array of page styles, -1 if style
* is not in that array
*/
public int indexOfPageStyle(TagStyle style) {
return styles.getIndexOf(style);
}
/**
* Returns an index of a {@link TagStyle styles} in an array of
* block styles.
* @param style the style which index will be checked
* @return an index of a style in an array of block styles, -1 if style
* is not in that array
*/
public int indexOfBlockStyle(TagStyle style) {
return styles.getIndexOf(style);
}
/**
* Returns an index of a {@link TagStyle styles} in an array of channel
* styles.
* @param style the style which index will be checked
* @return an index of a style in an array of channel styles, -1 if style
* is not in that array
*/
public int indexOfChannelStyle(TagStyle style) {
return styles.getIndexOf(style);
}
/**
* Returns all {@link Tag tagged selections}.
* @return all tagged selections
*/
public SortedSet<Tag> getTags() {
return tags;
}
/**
* Returns {@link Tag tagged selections} that may be between two
* given positions.
* Always remember that this method returns tags that MAY be between
* these two positions. It doesn't mean they actually ARE (!!!).
* You always need to verify this. The only guarantee being made is that
* ALL the tags that MAY be in this region are returned.
* This set is inclusive at both ends!
* @param start starting position
* @param end ending position
* @return set of tagged selections that may be between two
* given positions.
*/
public SortedSet<Tag> getTagsBetween(double start, double end) {
Tag startMarker = new Tag(null, start-maxTagLength, 0);
Tag endMarker = new Tag(null,end,Float.MAX_VALUE); // note that lengths matter, so that all tags starting at exactly end will be selected
return tags.subSet(startMarker, true, endMarker, true);
}
/**
* Returns the number of {@link Tag tagged selections}.
* @return the number of tagged selections
*/
public int getTagCount() {
return tags.size();
}
/**
* Returns the number of {@link Tag tagged selections} for a given
* {@link SignalSelectionType type} of a selection
* @param type the type of a selection
* @return the number of tagged selections for a given type of a selection
*/
public int getTagCount(SignalSelectionType type) {
if (type == SignalSelectionType.PAGE) {
return getPageTagCount();
}
else if (type == SignalSelectionType.BLOCK) {
return getBlockTagCount();
}
else if (type == SignalSelectionType.CHANNEL) {
return getChannelTagCount();
} else {
return 0;
}
}
/**
* Returns the number of tagged page {@link Tag selections}.
* @return the number of tagged page selections
*/
public int getPageTagCount() {
if (pageTagsCache == null) {
makeTagCache();
}
return pageTagsCache.size();
}
/**
* Returns the number of tagged block {@link Tag selections}.
* @return the number of tagged block selections
*/
public int getBlockTagCount() {
if (blockTagsCache == null) {
makeTagCache();
}
return blockTagsCache.size();
}
/**
* Returns the number of tagged channel {@link Tag selections}.
* @return the number of tagged channel selections
*/
public int getChannelTagCount() {
if (channelTagsCache == null) {
makeTagCache();
}
return channelTagsCache.size();
}
/**
* Returns the {@link Tag tagged selection} of a given index in an
* array of selections for a given {@link SignalSelectionType type}
* of a selection.
* @param type the type of a selection
* @param index the index in an array of selections for a given
* type of a selection
* @return the found tagged selection
*/
public Tag getTagAt(SignalSelectionType type, int index) {
if (type == SignalSelectionType.PAGE) {
return getPageTagAt(index);
}
else if (type == SignalSelectionType.BLOCK) {
return getBlockTagAt(index);
}
else if (type == SignalSelectionType.CHANNEL) {
return getChannelTagAt(index);
} else {
return null;
}
}
/**
* Returns the {@link Tag selection} of a given index in an array of
* tagged page selections.
* @param index the index in an array of tagged page selections
* @return the found tagged selection
*/
public Tag getPageTagAt(int index) {
if (pageTagsCache == null) {
makeTagCache();
}
return pageTagsCache.get(index);
}
/**
* Returns the {@link Tag selection} of a given index in an array of
* tagged block selections.
* @param index the index in an array of tagged block selections
* @return the found tagged selection
*/
public Tag getBlockTagAt(int index) {
if (blockTagsCache == null) {
makeTagCache();
}
return blockTagsCache.get(index);
}
/**
* Returns the {@link Tag selection} of a given index in an array of
* tagged channel selections.
* @param index the index in an array of tagged channel selections
* @return the found tagged selection
*/
public Tag getChannelTagAt(int index) {
if (channelTagsCache == null) {
makeTagCache();
}
return channelTagsCache.get(index);
}
/**
* Returns an index of a tagged {@link Tag selection} in an appropriate
* array.
* @param tag the selection which index will be checked
* @return an index of a tagged selection in an appropriate array,
* -1 if tagged selection is not in any array
*/
public int indexOfTag(Tag tag) {
SignalSelectionType type = tag.getType();
if (type == SignalSelectionType.PAGE) {
return indexOfPageTag(tag);
}
else if (type == SignalSelectionType.BLOCK) {
return indexOfBlockTag(tag);
}
else if (type == SignalSelectionType.CHANNEL) {
return indexOfChannelTag(tag);
} else {
return -1;
}
}
/**
* Returns an index of a {@link Tag selection} in an array of tagged
* page selections.
* @param tag the selection which index will be checked
* @return an index of a selections in an array of tagged page selections,
* -1 if selection is not in that array
*/
public int indexOfPageTag(Tag tag) {
if (pageTagsCache == null) {
makeTagCache();
}
return pageTagsCache.indexOf(tag);
}
/**
* Returns an index of a {@link Tag selection} in an array of tagged
* block selections.
* @param tag the selection which index will be checked
* @return an index of a selections in an array of tagged block selections,
* -1 if selection is not in that array
*/
public int indexOfBlockTag(Tag tag) {
if (blockTagsCache == null) {
makeTagCache();
}
return blockTagsCache.indexOf(tag);
}
/**
* Returns an index of a {@link Tag selection} in an array of tagged
* channel selections.
* @param tag the selection which index will be checked
* @return an index of a selections in an array of tagged channel selections,
* -1 if selection is not in that array
*/
public int indexOfChannelTag(Tag tag) {
if (channelTagsCache == null) {
makeTagCache();
}
return channelTagsCache.indexOf(tag);
}
/**
* Verifies if the length of {@link Tag selection} is a multiple of
* a block size for block selections and multiple of a page size
* for page selections.
* @return true if lengths are valid, false otherwise
*/
public boolean verifyTags() {
if (pageTagsCache == null || blockTagsCache == null) {
makeTagCache();
}
if (blockSize > 0) {
for (Tag tag : blockTagsCache) {
if (tag.getLength() != blockSize) {
logger.debug("Tag block size is [" + tag.getLength() + "] should be [" + blockSize + "]");
return false;
}
}
}
for (Tag tag : pageTagsCache) {
if (tag.getLength() != pageSize) {
logger.debug("Tag page size is [" + tag.getLength() + "] should be [" + pageSize + "]");
return false;
}
}
return true;
}
/**
* Verifies if the length of a given tagged {@link Tag selection} is
* a multiple of a block size for block selection or multiple of
* a page size for page selection.
* @param tag the tagged selection to be verified
* @return true if length is valid, false otherwise
*/
public boolean verifyTag(Tag tag) {
SignalSelectionType type = tag.getType();
if (type.isBlock()) {
if (tag.getLength() != blockSize) {
logger.debug("Tag block size is [" + tag.getLength() + "] should be [" + blockSize + "]");
return false;
}
} else if (type.isPage()) {
if (tag.getLength() != pageSize) {
logger.debug("Tag page size is [" + tag.getLength() + "] should be [" + pageSize + "]");
return false;
}
}
return true;
}
/**
* Adds a given {@link TagStyle style} to this set.
* @param style the tag style to be added
*/
public void addStyle(TagStyle style) {
styles.addStyle(style);
}
/**
* Removes the {@link TagStyle style} of a given name.
* @param name the name of a style to be removed
*/
public void removeStyle(TagStyle style) {
styles.removeStyle(style);
}
/**
* Sets the {@link TagStyle style} of a given name to a new value.
* @param name the name of a style
* @param style new style to be set
*/
public void updateStyle(String name, TagStyle style) {
styles.updateStyle(name, style);
}
/**
* Returns whether there are any {@link Tag tagged selections} of
* a given style.
* @param name the name of a style
* @return true if there are any tagged selections of a given style,
* false otherwise
*/
public boolean hasTagsWithStyle(TagStyle style) {
for (Tag tag : tags) {
if (tag.getStyle() == style) {
return true;
}
}
return false;
}
/**
* Adds a {@link Tag tagged selection} to this set.
* @param tag tagged selection to be added
* @throws SanityCheckException thrown if tag is not valid (invalid
* length for a given type)
*/
public void addTag(Tag tag) {
if (!verifyTag(tag)) {
throw new SanityCheckException("Tag not compatible");
}
TagStyle style = this.getStyle(tag.getStyle().getType(), tag.getStyle().getName());
if (style != null)
tag.setStyle(style);
tags.add(tag);
invalidateTagCache(tag.getStyle().getType());
if (maxTagLength < tag.getLength()) {
maxTagLength = tag.getLength();
}
fireTagAdded(tag);
}
/**
* Removes {@link Tag tagged selections} that intersect with
* a given selection and are of the same type as given.
* @param selection the selection to which tagged selections will be
* compared
*/
public void eraseTags(SignalSelection selection) {
// erase same type tags from selection
double selStart = selection.getPosition();
double selEnd = selStart + selection.getLength();
SortedSet<Tag> conflicts = getTagsBetween(selStart, selEnd);
double confStart;
double confEnd;
Tag confTag;
Iterator<Tag> it = conflicts.iterator();
SignalSelectionType type = selection.getType();
boolean calculateLength = false;
while (it.hasNext()) {
confTag = it.next();
if (confTag.getType() == type) {
// border condition needs recheck due to inclusivness difference
confStart = confTag.getPosition();
confEnd = confStart + confTag.getLength();
if ((confStart < selEnd) && (confEnd > selStart) && (confTag.getChannel() == selection.getChannel())) {
it.remove();
invalidateTagCache(type);
if (confTag.getLength() > (maxTagLength * 0.75)) {
calculateLength = true;
}
fireTagRemoved(confTag);
}
}
}
if (calculateLength) {
calculateMaxTagLength();
}
}
/**
* Removes {@link Tag tagged selections} that intersect with a given
* tagged selection and have the same type of a style. Adds the new tag.
* @param tag the tagged selection to which tagged selections will be
* compared and which will be added
*/
public void replaceSameTypeTags(Tag tag) {
// remove conflicting tags
double selStart = tag.getPosition();
double selEnd = selStart + tag.getLength();
SortedSet<Tag> conflicts = getTagsBetween(selStart, selEnd);
TagStyle confStyle;
double confStart;
double confEnd;
Tag confTag;
Iterator<Tag> it = conflicts.iterator();
SignalSelectionType type = tag.getStyle().getType();
boolean calculateLength = false;
while (it.hasNext()) {
confTag = it.next();
confStyle = confTag.getStyle();
if (confStyle.getType() == type) {
// border condition needs recheck due to inclusivness difference
confStart = confTag.getPosition();
confEnd = confStart + confTag.getLength();
if ((confStart < selEnd) && (confEnd > selStart) && (confTag.getChannel() == tag.getChannel())) {
it.remove();
invalidateTagCache(confStyle.getType());
if (confTag.getLength() > (maxTagLength * 0.75)) {
calculateLength = true;
}
fireTagRemoved(confTag);
}
}
}
if (calculateLength) {
calculateMaxTagLength();
}
addTag(tag);
}
/**
* Adds a given {@link Tag selection} to the collection of tagged
* selections. If any selection intersects with given and has the same
* type as given it is changed:
* 1) if has the same style as given it is merged with it,
* 2) if has a different style it is shortened so that it won't
* intersect with given any more.
* @param tag the tagged selection to be added
*/
public void splitAndMergeSameTypeTags(Tag tag) {
// split conflicting tags while merging same type tags
double selStart = tag.getPosition();
double selEnd = selStart + tag.getLength();
SortedSet<Tag> conflicts = getTagsBetween(selStart, selEnd);
TagStyle confStyle;
double confStart;
double confEnd;
Tag confTag;
Iterator<Tag> it = conflicts.iterator();
SignalSelectionType type = tag.getStyle().getType();
boolean calculateLength = false;
LinkedList<Tag> addedTags = new LinkedList<Tag>();
Tag addedTag;
double newSelStart = selStart;
double newSelEnd = selEnd;
while (it.hasNext()) {
confTag = it.next();
confStyle = confTag.getStyle();
if (confStyle.getType() == type) {
// border condition needs recheck due to inclusivness difference
confStart = confTag.getPosition();
confEnd = confStart + confTag.getLength();
if ((confStart <= selEnd) && (confEnd >= selStart) && (confTag.getChannel() == tag.getChannel())) {
if (confTag.getStyle() == tag.getStyle()) { // same type, merge
if (confStart < newSelStart) {
newSelStart = confStart;
}
if (confEnd > newSelEnd) {
newSelEnd = confEnd;
}
} else { // different type, split & replace
if ((confStart < selEnd) && (confEnd > selStart)) {
if (confStart < selStart) { // if the conflicting tag partially precedes this tag
addedTag = confTag.clone();
addedTag.setLength(selStart-confStart);
addedTags.add(addedTag);
}
if (confEnd > selEnd) { // if the conflicting tag partially follows this tag
addedTag = confTag.clone();
addedTag.setParameters(selEnd,confEnd-selEnd);
addedTags.add(addedTag);
}
} else {
continue; // don't change adjacent tags
}
}
it.remove();
invalidateTagCache(confStyle.getType());
if (confTag.getLength() > (maxTagLength * 0.75)) {
calculateLength = true;
}
fireTagRemoved(confTag);
}
}
}
tag.setParameters(newSelStart, newSelEnd-newSelStart);
if (calculateLength) {
calculateMaxTagLength();
}
for (Tag t : addedTags) {
addTag(t);
}
addTag(tag);
}
/**
* Adds a given channel {@link Tag selection} to the collection of
* tagged selections. If any channel selection intersects with given
* and has the same style as given it is merged with it.
* @param tag the tagged channel selection to be added
*/
public void mergeSameTypeChannelTags(Tag tag) {
// merge adjacent channel tags
TagStyle style = tag.getStyle();
SignalSelectionType type = style.getType();
if (type != SignalSelectionType.CHANNEL) {
addTag(tag);
return;
}
if (style.isMarker()) {
addTag(tag);
return;
}
double selStart = tag.getPosition();
double selEnd = selStart + tag.getLength();
double newSelStart = selStart;
double newSelEnd = selEnd;
SortedSet<Tag> conflicts = getTagsBetween(selStart, selEnd);
TagStyle confStyle;
double confStart;
double confEnd;
Tag confTag;
Iterator<Tag> it = conflicts.iterator();
boolean calculateLength = false;
while (it.hasNext()) {
confTag = it.next();
confStyle = confTag.getStyle();
if (confStyle.isMarker()) {
continue;
}
if (confStyle.getType() == SignalSelectionType.CHANNEL) {
// border condition needs recheck due to inclusivness difference
confStart = confTag.getPosition();
confEnd = confStart + confTag.getLength();
if ((confTag.getStyle() == tag.getStyle()) && (confStart <= selEnd) && (confEnd >= selStart) && (confTag.getChannel() == tag.getChannel())) {
it.remove();
invalidateTagCache(confStyle.getType());
if (confTag.getLength() > (maxTagLength * 0.75)) {
calculateLength = true;
}
fireTagRemoved(confTag);
if (confStart < newSelStart) {
newSelStart = confStart;
}
if (confEnd > newSelEnd) {
newSelEnd = confEnd;
}
}
}
}
tag.setParameters(newSelStart, newSelEnd-newSelStart);
if (calculateLength) {
calculateMaxTagLength();
}
addTag(tag);
}
/**
* Removes the {@link Tag tagged selection} from this set.
* @param tag the tagged selection to be removed
*/
public void removeTag(Tag tag) {
boolean removed = tags.remove(tag);
if (removed) {
invalidateTagCache(tag.getStyle().getType());
if (tag.getLength() > (maxTagLength * 0.75)) {
calculateMaxTagLength();
}
fireTagRemoved(tag);
}
}
/**
* Removes <i>oldTag</i> from {@link Tag tagged selections} list and
* adds <i>tag</i> to it.
* @param oldTag the tagged selection to be removed
* @param tag the tagged selection to be added
*/
public void updateTag(Tag oldTag, Tag tag) {
if (!verifyTag(tag)) {
throw new SanityCheckException("Tag not compatible");
}
boolean removed = tags.remove(oldTag);
if (removed) {
invalidateTagCache(tag.getStyle().getType());
if (tag.getLength() != oldTag.getLength() && oldTag.getLength() > (maxTagLength * 0.9)) {
calculateMaxTagLength();
}
}
tags.add(tag);
invalidateTagCache(tag.getStyle().getType());
if (maxTagLength < tag.getLength()) {
maxTagLength = tag.getLength();
}
fireTagChanged(tag, oldTag);
}
/**
* Verifies {@link Tag tag} and invalidates cache.
* @param tag tag that was edited
* @throws SanityCheckException if tag has invalid length (for a given
* type)
*/
public void editTag(Tag tag) {
if (!verifyTag(tag)) {
throw new SanityCheckException("Tag not compatible");
}
invalidateTagCache(tag.getStyle().getType());
fireTagChanged(tag, tag);
}
/**
* Returns the map associating {@link TagStyle tag styles} with
* KeyStrokes assigned to them.
* @return the map associating tag styles with KeyStrokes assign to them.
*/
public HashMap<KeyStroke, TagStyle> getStylesByKeyStrokes() {
return styles.getStylesByKeyStrokes();
}
/**
* Returns {@link TagStyle tag style} associated with a given key.
* @param keyStroke key to which tag style is associated
* @return tag style associated with a given key
*/
public TagStyle getStyleByKeyStroke(KeyStroke keyStroke) {
return getStylesByKeyStrokes().get(keyStroke);
}
/**
* Returns the {@link TagSignalIdentification identification} of the
* signal.
* @return the identification of the signal
*/
public TagSignalIdentification getTagSignalIdentification() {
return tagSignalIdentification;
}
/**
* Sets the {@link TagSignalIdentification identification} of the
* signal.
* @param tagSignalIdentification the identification of the signal
*/
public void setTagSignalIdentification(TagSignalIdentification tagSignalIdentification) {
this.tagSignalIdentification = tagSignalIdentification;
}
/**
* Returns the description of this tagged set.
* @return the description of this tagged set
*/
public String getInfo() {
return info;
}
/**
* Sets the description of this tagged set.
* @param info the description of this tagged set
*/
public void setInfo(String info) {
this.info = info;
}
/**
* Returns the description of the {@link #montage montage}.
* @return the description of the montage
*/
public String getMontageInfo() {
return montageInfo;
}
/**
* Sets the description of the {@link #montage montage}.
* @param montageInfo the description of the montage
*/
public void setMontageInfo(String montageInfo) {
this.montageInfo = montageInfo;
}
/**
* Returns estimated maximal length of a {@link Tag tagged selection}
* in <i>tags</i>.
* Note that this is just an estimate - may be 10% more than the
* actual length of the longest tag in the set.
* @return estimated maximal length of a tagged selection in <i>tags</i>.
*/
public double getMaxTagLength() {
return maxTagLength;
}
/**
* Adds a {@link TagListener TagListener} to the list of listeners.
* @param listener the TagListener to be added
*/
public void addTagListener(TagListener listener) {
listenerList.add(TagListener.class, listener);
}
/**
* Removes a {@link TagListener TagListener} from the list of listeners.
* @param listener the TagListener to be removed
*/
public void removeTagListener(TagListener listener) {
listenerList.remove(TagListener.class, listener);
}
/**
* Adds a {@link TagStyleListener TagStyleListener} to the list
* of listeners.
* @param listener the TagStyleListener to be added
*/
public void addTagStyleListener(TagStyleListener listener) {
styles.addTagStyleListener(listener);
}
/**
* Removes a {@link TagStyleListener TagStyleListener} from the list
* of listeners.
* @param listener the TagStyleListener to be removed
*/
public void removeTagStyleListener(TagStyleListener listener) {
styles.removeTagStyleListener(listener);
}
/**
* {@link TagStyleListener tag style listeners} that a
* {@link Tag tagged selection} of a given index has been added.
* @param tag the added tagged selection
*/
protected void fireTagAdded(Tag tag) {
Object[] listeners = listenerList.getListenerList();
TagEvent e = null;
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==TagListener.class) {
if (e == null) {
e = new TagEvent(this,tag,tag.getPosition(),tag.getPosition()+tag.getLength());
}
((TagListener)listeners[i+1]).tagAdded(e);
}
}
}
/**
* Fires {@link TagStyleListener tag style listeners} that a
* {@link Tag tagged selection} of a given index has been removed.
* @param tag the removed tagged selection
*/
protected void fireTagRemoved(Tag tag) {
Object[] listeners = listenerList.getListenerList();
TagEvent e = null;
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==TagListener.class) {
if (e == null) {
e = new TagEvent(this,tag,tag.getPosition(),tag.getPosition()+tag.getLength());
}
((TagListener)listeners[i+1]).tagRemoved(e);
}
}
}
/**
* Fires {@link TagStyleListener tag style listeners} that a
* {@link Tag tagged selection} of a given index has been changed.
* @param tag the new value of the changed tagged selection
* @param oldTag the old value of the changed tagged selection
*/
protected void fireTagChanged(Tag tag, Tag oldTag) {
Object[] listeners = listenerList.getListenerList();
TagEvent e = null;
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==TagListener.class) {
if (e == null) {
double start;
double end;
if (oldTag != null) {
start = Math.min(tag.getPosition(), oldTag.getPosition());
end = Math.max(tag.getPosition()+tag.getLength(), oldTag.getPosition()+oldTag.getLength());
} else {
start = tag.getPosition();
end = tag.getPosition() + tag.getLength();
}
e = new TagEvent(this,tag,start,end);
}
((TagListener)listeners[i+1]).tagChanged(e);
}
}
}
/**
* Invalidates the cache of {@link Tag tagged selections} of a given
* type.
* If cache is invalidated it will need to be built again before
* next usage.
* @param type the type of a selection
*/
private void invalidateTagCache(SignalSelectionType type) {
if (type == SignalSelectionType.PAGE) {
pageTagsCache = null;
} else if (type == SignalSelectionType.BLOCK) {
blockTagsCache = null;
} else if (type == SignalSelectionType.CHANNEL) {
channelTagsCache = null;
}
}
/**
* Creates the cache of {@link Tag tagged selections} for all types of
* selection (if they don't exist or are invalidated).
*/
private void makeTagCache() {
if (pageTagsCache != null && blockTagsCache != null && channelTagsCache != null) {
return;
}
ArrayList<Tag> newPageTags = null;
ArrayList<Tag> newBlockTags = null;
ArrayList<Tag> newChannelTags = null;
if (pageTagsCache == null) {
newPageTags = new ArrayList<Tag>();
}
if (blockTagsCache == null) {
newBlockTags = new ArrayList<Tag>();
}
if (channelTagsCache == null) {
newChannelTags = new ArrayList<Tag>();
}
TagStyle style;
SignalSelectionType type;
for (Tag tag : tags) {
style = tag.getStyle();
if (style != null) {
type = style.getType();
if (type == SignalSelectionType.PAGE && pageTagsCache == null) {
newPageTags.add(tag);
} else if (type == SignalSelectionType.BLOCK && blockTagsCache == null) {
newBlockTags.add(tag);
} else if (type == SignalSelectionType.CHANNEL && channelTagsCache == null) {
newChannelTags.add(tag);
}
}
}
if (pageTagsCache == null) {
pageTagsCache = newPageTags;
}
if (blockTagsCache == null) {
blockTagsCache = newBlockTags;
}
if (channelTagsCache == null) {
channelTagsCache = newChannelTags;
}
}
/**
* Calculates the maximum length of a {@link Tag tagged selection}.
*/
private void calculateMaxTagLength() {
double maxTagLength = 0;
for (Tag tag : tags) {
if (maxTagLength < tag.getLength()) {
maxTagLength = tag.getLength();
}
}
this.maxTagLength = maxTagLength;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
@Override
public StyledTagSet clone() {
StyledTagSet newTagSet = new StyledTagSet(styles);
newTagSet.setName(name);
return newTagSet;
}
/**
* Copies all styles from the given {@link StyledTagSet} into this tag set.
* Deletes all styles that are not defined in the given StyledTagSet
* unless some tags exist in this StyledTagSet which use the mentioned
* style. In that case the style is not removed.
* @param tagSet the tag set from which styles should be removed.
* @return the list of tag styles names which couldn't be removed from
* this tag set.
*/
public List<String> copyStylesFrom(StyledTagSet tagSet) {
List<String> stylesThatCouldNotBeDeleted = new ArrayList<String>();
Set<TagStyle> stylesFromBoth = new HashSet<TagStyle>();
stylesFromBoth.addAll(this.getListOfStyles());
stylesFromBoth.addAll(tagSet.getListOfStyles());
//for each tag check if it should be removed, updated or deleted
for (TagStyle style: stylesFromBoth) {
SignalSelectionType styleType = style.getType();
TagStyle newStyle = tagSet.getStyle(styleType, style.getName());
TagStyle oldStyle = this.getStyle(styleType, style.getName());
if (newStyle != null && oldStyle != null) {
updateStyle(oldStyle.getName(), newStyle);
}
else if (newStyle == null && oldStyle != null) {
if (!tryToRemoveStyle(oldStyle)) {
stylesThatCouldNotBeDeleted.add(oldStyle.getName());
}
}
else if (newStyle != null && oldStyle == null) {
addStyle(newStyle);
}
}
//invalidate all caches
invalidateTagCache(SignalSelectionType.PAGE);
invalidateTagCache(SignalSelectionType.BLOCK);
invalidateTagCache(SignalSelectionType.CHANNEL);
//connect tags with appropriate tag styles (after update the reference
//may be not correct any more
updateTagAttributesBinding();
return stylesThatCouldNotBeDeleted;
}
/**
* Tries to remove the given style. If a tag exists that uses this style,
* it cannot be removed and false is returned. If no tag exists that uses
* this style, it is removed and 'true' is returned.
* @param style the style to be removed
* @return true if the style was sucessfully removed, false otherwise
*/
protected boolean tryToRemoveStyle(TagStyle style) {
boolean doTagsWithThisStyleExist = false;
Iterator<Tag> tagIterator = tags.iterator();
while (tagIterator.hasNext()) {
Tag tag = tagIterator.next();
if (tag.getStyle().getName().equals(style.getName())
&& tag.getStyle().getType() == style.getType()) {
doTagsWithThisStyleExist = true;
break;
}
}
if (!doTagsWithThisStyleExist) {
styles.removeStyle(style);
return true;
}
else {
return false;
}
}
/**
* Checks if {@link TagStyle tag styles} {@link TagStyleAttributeDefinition}
* is properly referenced by each {@link TagAttributeValue} and
* - in case it is not - the reference is corrected.
* Explanation: each tag style contains {@link TagStyleAttributeDefinition
* attributes definitions} which are referenced by each tag that uses
* these definitions. But, after copying styles from some other tagSet
* (which occurs when applying a preset to a tag set) those references
* will be incorrect.
*/
protected void updateTagAttributesBinding() {
Iterator<Tag> tagIterator = tags.iterator();
while (tagIterator.hasNext()) {
Tag tag = tagIterator.next();
String styleName = tag.getStyle().getName();
SignalSelectionType tagType = tag.getStyle().getType();
TagStyle style = getStyle(tagType, styleName);
tag.setStyle(style);
for (TagAttributeValue value: tag.getAttributes().getAttributesList()) {
TagStyleAttributeDefinition oldAttributeDefinition = value.getAttributeDefinition();
TagStyleAttributeDefinition newAttributeDefinition = style.getAttributesDefinitions().getAttributeDefinition(oldAttributeDefinition.getCode());
if (oldAttributeDefinition != null && newAttributeDefinition == null) {
style.getAttributesDefinitions().addAttributeDefinition(oldAttributeDefinition);
value.setAttributeDefinition(oldAttributeDefinition);
}
else {
value.setAttributeDefinition(newAttributeDefinition);
}
}
}
}
@Override
public boolean equals(Object obj) {
if (obj instanceof StyledTagSet) {
StyledTagSet sts = (StyledTagSet) obj;
return sts.getName().equals(this.getName());
}
return false;
}
}