/* TagDifferenceDetector.java created 2007-11-13
*
*/
package org.signalml.domain.tag;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import org.signalml.app.document.TagDocument;
import org.signalml.app.document.signal.SignalDocument;
import org.signalml.exception.SanityCheckException;
import org.signalml.plugin.export.signal.Document;
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;
/**
* This class represents the detector of {@link TagDifference differences}
* between {@link Tag tags} from two sets. Allows to compare two sets of
* tags and find their difference (that is set of differences between tags).
*
* @author Michal Dobaczewski © 2007-2008 CC Otwarte Systemy Komputerowe Sp. z o.o.
*/
public class TagDifferenceDetector {
/**
* Calculates the differences between two sets of {@link Tag tags}
* of the given {@link SignalSelectionType type}.
* @param topTags the first set of tags (top)
* @param bottomTags the second set of tags (bottom)
* @param targetType the type signal selection
* @param channel the number of the channel which tags concern
* @param differences the set in which calculated differences will be
* remembered
*/
public void getDifferences(SortedSet<Tag> topTags, SortedSet<Tag> bottomTags, final SignalSelectionType targetType, final int channel, final TreeSet<TagDifference> differences) {
TaggedFragmentProcessor processor = new TaggedFragmentProcessor() {
Tag tag = null;
Iterator<Tag> it;
Iterator<Tag> it2;
TagDifference difference;
boolean tagsAreTheSame;
boolean found;
@Override
public void process(List<Tag> activeTopTags, List<Tag> activeBottomTags, double start, double end) {
// now we have established that in the time period from
// timePointer to nextInterestingEvent respective active tags of the given
// type exist for top & bottom tag. Let's compare them.
if (activeTopTags.isEmpty() && activeBottomTags.isEmpty()) {
// no tags - no difference
difference = null;
} else if (!activeTopTags.isEmpty() && activeBottomTags.isEmpty()) {
// tag missing at the bottom
difference = new TagDifference(targetType,start,end-start,channel,TagDifferenceType.MISSING_IN_BOTTOM);
} else if (!activeBottomTags.isEmpty() && activeTopTags.isEmpty()) {
// tag missing at the top
difference = new TagDifference(targetType,start,end-start,channel,TagDifferenceType.MISSING_IN_TOP);
} else {
// there are some tags both top & bottom - are they the same?
tagsAreTheSame = false;
if (activeBottomTags.size() == activeTopTags.size()) {
it = activeTopTags.iterator();
found = false;
while (it.hasNext()) {
tag = it.next();
it2 = activeBottomTags.iterator();
found = false;
while (it2.hasNext()) {
if (it2.next().getStyle().getName().equals(tag.getStyle().getName())) {
found = true;
break;
}
}
if (!found) {
break;
}
}
if (found) {
tagsAreTheSame = true;
}
}
if (!tagsAreTheSame) {
difference = new TagDifference(targetType,start,end-start,channel,TagDifferenceType.DIFFERENT);
} else {
difference = null;
}
}
if (difference != null) {
differences.add(difference);
}
}
};
iterate(topTags, bottomTags, targetType, channel, processor, -1);
}
/**
* Compares two sets of {@link Tag tags} of the given
* {@link SignalSelectionType type}.
* As a result creates {@link TagComparisonResult TagComparisonResult}.
* @param topStyles possible styles of tags in the first set (top)
* @param bottomStyles possible styles of tags in the second set (bottom)
* @param topTags the first set of tags (top)
* @param bottomTags the second set of tags (bottom)
* @param targetType the type signal selection
* @param channel the number of the channel which tags concern
* @param signalLength the length of entire signal
* @return the created TagComparisonResult object
*/
public TagComparisonResult compare(TagStyle[] topStyles, TagStyle[] bottomStyles, SortedSet<Tag> topTags, SortedSet<Tag> bottomTags, SignalSelectionType targetType, int channel, double signalLength) {
final TagComparisonResult result = new TagComparisonResult(topStyles, bottomStyles, signalLength, signalLength);
TaggedFragmentProcessor processor = new TaggedFragmentProcessor() {
private Iterator<Tag> topIterator;
private Iterator<Tag> bottomIterator;
private TagStyle topTagStyle;
private TagStyle bottomTagStyle;
private double length;
private boolean topEmpty;
private boolean bottomEmpty;
@Override
public void process(List<Tag> activeTopTags, List<Tag> activeBottomTags, double start, double end) {
length = end - start;
if (length <= 0) {
return;
}
topEmpty = activeTopTags.isEmpty();
bottomEmpty = activeBottomTags.isEmpty();
if (topEmpty && bottomEmpty) {
result.addTopStyleTime(-1, length);
result.addBottomStyleTime(-1, length);
result.addStyleOverlay(-1, -1, length);
return;
}
if (!bottomEmpty) {
bottomIterator = activeBottomTags.iterator();
while (bottomIterator.hasNext()) {
bottomTagStyle = bottomIterator.next().getStyle();
result.addBottomStyleTime(bottomTagStyle, length);
if (topEmpty) {
result.addTopStyleTime(-1, length);
result.addStyleOverlay(null, bottomTagStyle, length);
}
}
}
if (!topEmpty) {
topIterator = activeTopTags.iterator();
while (topIterator.hasNext()) {
topTagStyle = topIterator.next().getStyle();
result.addTopStyleTime(topTagStyle, length);
if (bottomEmpty) {
result.addBottomStyleTime(-1, length);
result.addStyleOverlay(topTagStyle, null, length);
} else {
bottomIterator = activeBottomTags.iterator();
while (bottomIterator.hasNext()) {
bottomTagStyle = bottomIterator.next().getStyle();
result.addStyleOverlay(topTagStyle, bottomTagStyle, length);
}
}
}
}
}
};
iterate(topTags, bottomTags, targetType, channel, processor, signalLength);
return result;
}
/**
* Fills two arrays of {@link TagStyle tag styles} (one for bottom tags
* and one for top) beginning with (sorted by name) styles that are
* in both sets (top and bottom) and after them putting
* (also sorted) the rest.
* @param topStyles the set of styles for top tags
* @param bottomStyles the set of styles for bottom tags
* @param topArr an array for top styles that will be filled
* @param bottomArr an array for bottom styles that will be filled
*/
private void arrangeTagStyles(Collection<TagStyle> topStyles, Collection<TagStyle> bottomStyles, TagStyle[] topArr, TagStyle[] bottomArr) {
LinkedHashMap<String,TagStyle> topMap = new LinkedHashMap<String, TagStyle>();
LinkedHashMap<String,TagStyle> bottomMap = new LinkedHashMap<String, TagStyle>();
TagStyleNameComparator comparator = new TagStyleNameComparator();
LinkedList<TagStyle> matching = new LinkedList<TagStyle>();
LinkedList<TagStyle> assorted = new LinkedList<TagStyle>();
int cnt = 0;
for (TagStyle style : bottomStyles) {
bottomMap.put(style.getName(), style);
}
for (TagStyle style : topStyles) {
topMap.put(style.getName(), style);
if (bottomMap.containsKey(style.getName())) {
matching.add(style);
} else {
assorted.add(style);
}
}
Collections.sort(matching, comparator);
Collections.sort(assorted, comparator);
for (TagStyle style : matching) {
topArr[cnt] = style;
cnt++;
}
for (TagStyle style : assorted) {
topArr[cnt] = style;
cnt++;
}
matching.clear();
assorted.clear();
for (TagStyle style : bottomStyles) {
if (topMap.containsKey(style.getName())) {
matching.add(style);
} else {
assorted.add(style);
}
}
Collections.sort(matching, comparator);
Collections.sort(assorted, comparator);
cnt = 0;
for (TagStyle style : matching) {
bottomArr[cnt] = style;
cnt++;
}
for (TagStyle style : assorted) {
bottomArr[cnt] = style;
cnt++;
}
}
/**
* Compares two sets of f {@link Tag tags} (all
* {@link SignalSelectionType types}).
* As a result creates {@link TagComparisonResults TagComparisonResults}.
* @param topTagDocument the {@link Document document} with possible
* styles of top tags
* @param bottomTagDocument the document with possible styles of
* bottom tags
* @return the created TagComparisonResults object
*/
public TagComparisonResults compare(TagDocument topTagDocument, TagDocument bottomTagDocument) {
SignalDocument parent = topTagDocument.getParent();
if (parent != bottomTagDocument.getParent()) {
throw new SanityCheckException("Cannot compare tags on different signals");
}
int channelCount = parent.getChannelCount();
double signalLength = parent.getMaxSignalLength();
StyledTagSet topTagSet = topTagDocument.getTagSet();
StyledTagSet bottomTagSet = bottomTagDocument.getTagSet();
TagStyle[] topStyles;
TagStyle[] bottomStyles;
SortedSet<Tag> topTags = topTagSet.getTags();
SortedSet<Tag> bottomTags = bottomTagSet.getTags();
Collection<TagStyle> topStyleSet = topTagSet.getPageStylesNoMarkers();
Collection<TagStyle> bottomStyleSet = bottomTagSet.getPageStylesNoMarkers();
topStyles = new TagStyle[topStyleSet.size()];
bottomStyles = new TagStyle[bottomStyleSet.size()];
arrangeTagStyles(topStyleSet, bottomStyleSet, topStyles, bottomStyles);
TagComparisonResult pageComparisonResult = compare(topStyles, bottomStyles, topTags, bottomTags, SignalSelectionType.PAGE, SignalSelection.CHANNEL_NULL, signalLength);
topStyleSet = topTagSet.getBlockStylesNoMarkers();
bottomStyleSet = bottomTagSet.getBlockStylesNoMarkers();
topStyles = new TagStyle[topStyleSet.size()];
bottomStyles = new TagStyle[bottomStyleSet.size()];
arrangeTagStyles(topStyleSet, bottomStyleSet, topStyles, bottomStyles);
TagComparisonResult blockComparisonResult = compare(topStyles, bottomStyles, topTags, bottomTags, SignalSelectionType.BLOCK, SignalSelection.CHANNEL_NULL, signalLength);
topStyleSet = topTagSet.getChannelStylesNoMarkers();
bottomStyleSet = bottomTagSet.getChannelStylesNoMarkers();
topStyles = new TagStyle[topStyleSet.size()];
bottomStyles = new TagStyle[bottomStyleSet.size()];
arrangeTagStyles(topStyleSet, bottomStyleSet, topStyles, bottomStyles);
TagComparisonResult[] channelComparisonResults = new TagComparisonResult[channelCount + 1]; // +1 for SignalSelection.CHANNEL_NULL
for (int i=0; i<channelCount; i++) {
channelComparisonResults[i] = compare(topStyles, bottomStyles, topTags, bottomTags, SignalSelectionType.CHANNEL, i, signalLength);
}
channelComparisonResults[channelCount] = compare(topStyles, bottomStyles, topTags, bottomTags, SignalSelectionType.CHANNEL, SignalSelection.CHANNEL_NULL, signalLength);
return new TagComparisonResults(pageComparisonResult, blockComparisonResult, channelComparisonResults);
}
private void iterate(SortedSet<Tag> topTags, SortedSet<Tag> bottomTags, SignalSelectionType targetType, int channel, TaggedFragmentProcessor processor, double signalLength) {
Iterator<Tag> topIterator = topTags.iterator();
Iterator<Tag> bottomIterator = bottomTags.iterator();
LinkedList<Tag> activeTopTags = new LinkedList<Tag>();
LinkedList<Tag> activeBottomTags = new LinkedList<Tag>();
Iterator<Tag> it;
Tag tag = null;
Tag endingTopTag = null;
Tag endingBottomTag = null;
Tag addedTopTag = null;
Tag addedBottomTag = null;
Tag topTag = null;
Tag bottomTag = null;
Tag tagCandidate = null;
double timePointer = 0;
double nextInterestingEvent;
boolean finished = false;
boolean channelMode = targetType.isChannel();
boolean hasEvent = false;
do {
endingTopTag = null;
endingBottomTag = null;
addedTopTag = null;
addedBottomTag = null;
nextInterestingEvent = Double.MAX_VALUE;
hasEvent = false;
if (!activeTopTags.isEmpty()) {
it = activeTopTags.iterator();
while (it.hasNext()) {
tag = it.next();
if (tag.getEndPosition() <= nextInterestingEvent) {
nextInterestingEvent = tag.getEndPosition();
hasEvent = true;
endingTopTag = tag;
}
}
}
if (!activeBottomTags.isEmpty()) {
it = activeBottomTags.iterator();
while (it.hasNext()) {
tag = it.next();
if (tag.getEndPosition() <= nextInterestingEvent) {
nextInterestingEvent = tag.getEndPosition();
hasEvent = true;
endingBottomTag = tag;
endingTopTag = null;
}
}
}
if (topTag == null) {
while (topIterator.hasNext()) {
tagCandidate = topIterator.next();
if (tagCandidate.getType() == targetType) {
if (!channelMode || tagCandidate.getChannel() == channel) {
if (!tagCandidate.isMarker()) {
topTag = tagCandidate;
break;
}
}
}
}
}
if (topTag != null) {
if (topTag.getPosition() <= nextInterestingEvent) {
nextInterestingEvent = topTag.getPosition();
hasEvent = true;
addedTopTag = topTag;
endingTopTag = null;
endingBottomTag = null;
}
}
if (bottomTag == null) {
while (bottomIterator.hasNext()) {
tagCandidate = bottomIterator.next();
if (tagCandidate.getType() == targetType) {
if (!channelMode || tagCandidate.getChannel() == channel) {
if (!tagCandidate.isMarker()) {
bottomTag = tagCandidate;
break;
}
}
}
}
}
if (bottomTag != null) {
if (bottomTag.getPosition() <= nextInterestingEvent) {
nextInterestingEvent = bottomTag.getPosition();
hasEvent = true;
addedBottomTag = bottomTag;
addedTopTag = null;
endingTopTag = null;
endingBottomTag = null;
}
}
if (!hasEvent) {
if (signalLength >= 0 && signalLength > timePointer) {
// process end fragment
processor.process(activeTopTags, activeBottomTags, timePointer, signalLength);
}
finished = true;
} else {
if (nextInterestingEvent > timePointer) {
processor.process(activeTopTags, activeBottomTags, timePointer, (signalLength >= 0 && nextInterestingEvent > signalLength) ? signalLength : nextInterestingEvent);
}
// now consume event
if (addedTopTag != null) {
activeTopTags.add(addedTopTag);
topTag = null;
}
if (addedBottomTag != null) {
activeBottomTags.add(addedBottomTag);
bottomTag = null;
}
if (endingTopTag != null) {
activeTopTags.remove(endingTopTag);
}
if (endingBottomTag != null) {
activeBottomTags.remove(endingBottomTag);
}
// move time pointer
timePointer = nextInterestingEvent;
}
} while (!finished);
}
private interface TaggedFragmentProcessor {
void process(List<Tag> activeTopTags, List<Tag> activeBottomTags, double start, double end);
}
}