+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<SequenceFeature> findFeatures(int fromColumn, int toColumn,
+ String... types)
+ {
+ int startPos = findPosition(fromColumn - 1); // convert base 1 to base 0
+ int endPos = fromColumn == toColumn ? startPos
+ : findPosition(toColumn - 1);
+
+ List<SequenceFeature> result = getFeatures().findFeatures(startPos,
+ endPos, types);
+
+ /*
+ * if end column is gapped, endPos may be to the right,
+ * and we may have included adjacent or enclosing features;
+ * remove any that are not enclosing, non-contact features
+ */
+ if (endPos > this.end || Comparison.isGap(sequence[toColumn - 1]))
+ {
+ ListIterator<SequenceFeature> it = result.listIterator();
+ while (it.hasNext())
+ {
+ SequenceFeature sf = it.next();
+ int sfBegin = sf.getBegin();
+ int sfEnd = sf.getEnd();
+ int featureStartColumn = findIndex(sfBegin);
+ if (featureStartColumn > toColumn)
+ {
+ it.remove();
+ }
+ else if (featureStartColumn < fromColumn)
+ {
+ int featureEndColumn = sfEnd == sfBegin ? featureStartColumn
+ : findIndex(sfEnd);
+ if (featureEndColumn < fromColumn)
+ {
+ it.remove();
+ }
+ else if (featureEndColumn > toColumn && sf.isContactFeature())
+ {
+ /*
+ * remove an enclosing feature if it is a contact feature
+ */
+ it.remove();
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Invalidates any stale cursors (forcing recalculation) by incrementing the
+ * token that has to match the one presented by the cursor
+ */
+ @Override
+ public void sequenceChanged()
+ {
+ changeCount++;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int replace(char c1, char c2)
+ {
+ if (c1 == c2)
+ {
+ return 0;
+ }
+ int count = 0;
+ synchronized (sequence)
+ {
+ for (int c = 0; c < sequence.length; c++)
+ {
+ if (sequence[c] == c1)
+ {
+ sequence[c] = c2;
+ count++;
+ }
+ }
+ }
+ if (count > 0)
+ {
+ sequenceChanged();
+ }
+
+ return count;
+ }
+
+ @Override
+ public List<SequenceFeature[]> adjustFeatures(int fromColumn, int toColumn)
+ {
+ List<SequenceFeature[]> amended = new ArrayList<>();
+
+ if (toColumn < fromColumn)
+ {
+ return amended;
+ }
+
+ synchronized (sequenceFeatureStore)
+ {
+ /*
+ * get features that overlap or span the cut region
+ */
+ List<SequenceFeature> overlaps = findFeatures(fromColumn, toColumn);
+ int cutWidth = toColumn - fromColumn + 1;
+
+ /*
+ * get features that strictly follow the cut region,
+ * and shift them left by the width of the cut
+ */
+ List<SequenceFeature> follow = findFeatures(toColumn + 1,
+ Integer.MAX_VALUE);
+ follow.removeAll(overlaps);
+ for (SequenceFeature sf : follow)
+ {
+ SequenceFeature copy = new SequenceFeature(sf, sf.getBegin()
+ - cutWidth, sf.getEnd() - cutWidth, sf.getFeatureGroup(),
+ sf.getScore());
+ deleteFeature(sf);
+ addSequenceFeature(copy);
+ }
+
+ /*
+ * adjust start-end of overlapping features, and delete if enclosed by
+ * the cut, or a partially overlapping contact feature
+ */
+ for (SequenceFeature sf : overlaps)
+ {
+ // TODO recode to compute newBegin, newEnd, isDelete
+ // then perform the action
+ int sfBegin = sf.getBegin();
+ int sfEnd = sf.getEnd();
+ int startCol = findIndex(sfBegin);
+ int endCol = findIndex(sfEnd);
+ if (startCol >= fromColumn && endCol <= toColumn)
+ {
+ // within cut region - delete feature
+ deleteFeature(sf);
+ amended.add(new SequenceFeature[] { sf, null });
+ continue;
+ }
+ if (startCol < fromColumn && endCol > toColumn)
+ {
+ // feature spans cut region - shift end left
+ SequenceFeature copy = new SequenceFeature(sf, sf.getBegin(),
+ sf.getEnd() - cutWidth, sf.getFeatureGroup(),
+ sf.getScore());
+ deleteFeature(sf);
+ addSequenceFeature(copy);
+ amended.add(new SequenceFeature[] { sf, copy });
+ continue;
+ }
+ // todo partial overlap - delete if contact feature
+ }
+ }
+
+ return amended;
+ }