package com.vistatec.ocelot.segment.model;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import com.google.common.collect.Lists;
import com.google.common.collect.Range;
import com.vistatec.ocelot.segment.model.enrichment.Enrichment;
import com.vistatec.ocelot.segment.model.enrichment.TranslationEnrichment;
import com.vistatec.ocelot.segment.view.SegmentVariantSelection;
public abstract class BaseSegmentVariant implements SegmentVariant {
private boolean enriched;
private boolean sentToFreme;
private Set<Enrichment> enrichments;
private TranslationEnrichment transEnrichment;
private boolean dirty;
protected List<HighlightData> highlightDataList;
protected int currentHighlightedIndex = -1;
protected abstract void setAtoms(List<SegmentAtom> atoms);
List<SegmentAtom> getAtomsForRange(int start, int length) {
List<SegmentAtom> atomsForRange = Lists.newArrayList();
int index = 0;
int end = start + length;
for (SegmentAtom atom : getAtoms()) {
if (index == start && atom instanceof PositionAtom) {
// Catch PositionAtom at the very beginning of the range.
atomsForRange.add(atom);
}
if (index >= end) {
return atomsForRange;
}
if (index + atom.getLength() > start) {
if (atom instanceof CodeAtom) {
atomsForRange.add(atom);
} else if (atom instanceof PositionAtom) {
atomsForRange.add(atom);
} else {
int min = Math.max(start - index, 0);
int max = Math.min(end - index, atom.getData().length());
atomsForRange.add(new TextAtom(atom.getData().substring(
min, max)));
}
}
index += atom.getLength();
}
return atomsForRange;
}
@Override
public SegmentAtom getAtomAt(int offset) {
int index = 0;
for (SegmentAtom atom : getAtoms()) {
if (index <= offset && offset < index + atom.getLength()) {
return atom;
}
index += atom.getLength();
}
return null;
}
public int getLength() {
int len = 0;
for (SegmentAtom atom : getAtoms()) {
len += atom.getLength();
}
return len;
}
@Override
public String getDisplayText() {
StringBuilder sb = new StringBuilder();
for (SegmentAtom atom : getAtoms()) {
sb.append(atom.getData());
}
return sb.toString();
}
@Override
public List<String> getStyleData(boolean verbose) {
ArrayList<String> textToStyle = new ArrayList<String>();
for (SegmentAtom atom : getAtoms()) {
if (atom instanceof CodeAtom && verbose) {
textToStyle.add(((CodeAtom)atom).getVerboseData());
textToStyle.add(atom.getTextStyle());
}
else if(atom instanceof TextAtom) {
TextAtom txtAtom = (TextAtom)atom;
if(txtAtom.getHighlightBoundaries() != null){
int index = 0;
for (int i = 0; i < txtAtom.getHighlightBoundaries().size(); i++) {
String style = null;
if (i == txtAtom.getCurrentHLBoundaryIdx()) {
style = txtAtom.getCurrHighlightStyle();
} else {
style = txtAtom.getHighlightStyle();
}
textToStyle.add(atom.getData().substring(
index,
txtAtom.getHighlightBoundaries().get(i)
.getFirstIndex()));
textToStyle.add(txtAtom.getTextStyle());
textToStyle.add(atom.getData().substring(
txtAtom.getHighlightBoundaries().get(i)
.getFirstIndex(),
txtAtom.getHighlightBoundaries().get(i)
.getLastIndex()));
textToStyle.add(style);
index = txtAtom.getHighlightBoundaries().get(i)
.getLastIndex();
}
if(txtAtom.getData().length() > index){
textToStyle.add(atom.getData().substring(index));
textToStyle.add(txtAtom.getTextStyle());
}
} else {
textToStyle.add(atom.getData());
textToStyle.add(atom.getTextStyle());
}
} else if (atom instanceof PositionAtom) {
// Skip
} else {
textToStyle.add(atom.getData());
textToStyle.add(atom.getTextStyle());
}
}
return textToStyle;
}
@Override
public boolean containsTag(int offset, int length) {
return checkForCode(offset, length).size() > 0;
}
@Override
public int findSelectionStart(int selectionStart) {
while (containsTag(selectionStart, 0)) {
selectionStart--;
}
return selectionStart;
}
@Override
public int findSelectionEnd(int selectionEnd) {
while (containsTag(selectionEnd, 0)) {
selectionEnd++;
}
return selectionEnd;
}
@Override
public boolean canInsertAt(int offset) {
return checkForCode(offset, 0).size() == 0;
}
// Returns list of codes that occur in the specified range
private List<CodeAtom> checkForCode(int offset, int length) {
List<CodeAtom> codes = Lists.newArrayList();
int offsetEnd = offset + length;
int index = 0;
for (SegmentAtom atom : getAtoms()) {
if (index > offsetEnd) {
// We've drifted out of the danger zone
return codes;
}
if (atom instanceof CodeAtom) {
CodeAtom code = (CodeAtom) atom;
if (offsetEnd > index && offset < index + code.getLength()) {
codes.add(code);
}
}
index += atom.getLength();
}
return codes;
}
@Override
public PositionAtom createPosition(int offset) {
List<SegmentAtom> atoms = Lists.newArrayList();
atoms.addAll(getAtomsForRange(0, offset));
PositionAtom position = new PositionAtom(this);
atoms.add(position);
atoms.addAll(getAtomsForRange(offset, getLength()));
setAtoms(atoms);
return position;
}
@Override
public void replaceSelection(int selectionStart, int selectionEnd,
SegmentVariantSelection rsv) {
BaseSegmentVariant sv = (BaseSegmentVariant) rsv.getVariant();
List<SegmentAtom> replaceAtoms = sv.getAtomsForRange(
rsv.getSelectionStart(),
rsv.getSelectionEnd() - rsv.getSelectionStart());
replaceSelection(selectionStart, selectionEnd, replaceAtoms);
}
@Override
public void replaceSelection(int selectionStart, int selectionEnd, List<? extends SegmentAtom> atoms) {
if (selectionStart == selectionEnd && atoms.isEmpty()) {
// No-op
return;
}
List<SegmentAtom> newAtoms = Lists.newArrayList();
newAtoms.addAll(getAtomsForRange(0, selectionStart));
newAtoms.addAll(atoms);
newAtoms.addAll(getAtomsForRange(selectionEnd, getLength()));
setAtoms(mergeNeighboringTextAtoms(newAtoms));
dirty = true;
// // Clean up codes that may be duplicates
// Set<String> codeIds = new HashSet<String>();
// List<SegmentAtom> cleanedAtoms = Lists.newArrayList();
// // Strip any atoms that exist twice
// for (SegmentAtom atom : newAtoms) {
// if (atom instanceof CodeAtom) {
// String id = ((CodeAtom) atom).getId();
// if (!codeIds.contains(id)) {
// codeIds.add(id);
// cleanedAtoms.add(atom);
// }
// } else {
// cleanedAtoms.add(atom);
// }
// }
// // Append any atoms that were deleted
// List<CodeAtom> originalCodes = findCodes(getAtoms());
// for (CodeAtom code : originalCodes) {
// if (!codeIds.contains(code.getId())) {
// cleanedAtoms.add(code);
// }
// }
// setAtoms(cleanedAtoms);
}
@Override
public void clearSelection(int selectionStart, int selectionEnd) {
replaceSelection(selectionStart, selectionEnd, Collections.<SegmentAtom> emptyList());
}
@Override
public boolean needsValidation() {
return dirty;
}
@Override
public boolean validateAgainst(SegmentVariant sv) {
List<CodeAtom> theseCodes = findCodes(getAtoms());
List<CodeAtom> thoseCodes = findCodes(sv.getAtoms());
if (theseCodes.size() != thoseCodes.size()) {
return false;
}
Set<String> codeIds = new HashSet<String>();
for (CodeAtom code : theseCodes) {
codeIds.add(code.getId());
}
for (CodeAtom code : thoseCodes) {
if (!codeIds.contains(code.getId())) {
return false;
}
}
return true;
}
@Override
public List<CodeAtom> getMissingTags(SegmentVariant sv) {
List<CodeAtom> theseCodes = findCodes(getAtoms());
List<CodeAtom> thoseCodes = findCodes(sv.getAtoms());
List<CodeAtom> missing = Lists.newArrayList();
for (CodeAtom code : thoseCodes) {
if (!theseCodes.contains(code)) {
missing.add(code);
}
}
return missing;
}
/**
* Modify the text contents of the segment; Assumes checks for attempting to
* change parts of the segment's unmodifiable text (such as protected codes)
* was already made.
*
* @param insertCharacterOffset
* - Character position indexed from the beginning of the segment
* to start inserting text
* @param charsToReplace
* - Number of characters to delete in the original segment text
* starting from the {@code insertionOffset}
* @param newText
* - String to insert at the {@code insertionOffset}; Swing sets
* to {@code null} if no text to insert
*/
@Override
public void modifyChars(int insertCharacterOffset, int charsToReplace,
String newText) {
int caretPosition = 0;
List<SegmentAtom> atoms = getAtoms();
List<SegmentAtom> newAtoms = Lists.newArrayList();
boolean done = false;
boolean insertingText = newText != null;
for (SegmentAtom atom : atoms) {
Range<Integer> atomCharacterRange = Range.closedOpen(caretPosition,
caretPosition + atom.getLength());
if (atomCharacterRange.contains(insertCharacterOffset) && !done) {
if (atom instanceof CodeAtom) {
// Assume inserting at the start of a CodeAtom;
// append handled by next atom (after for-loop if last atom)
if (insertingText) {
// Ignore incrementing caret for newText; delete/replace
// based off of original text.
newAtoms.add(new TextAtom(newText));
}
newAtoms.add(atom);
done = true;
} else if (atom instanceof TextAtom) {
String origAtomText = atom.getData();
int atomCharInsertionIndex = Math.max(insertCharacterOffset
- caretPosition, 0);
newAtoms.add(new TextAtom(origAtomText.substring(0,
atomCharInsertionIndex)));
if (insertingText) {
// Ignore incrementing caret for newText; delete/replace
// based off of original text.
newAtoms.add(new TextAtom(newText));
}
// Ignore decreasing caret for removing text; delete/replace
// based off of original text.
newAtoms.add(new TextAtom(origAtomText
.substring(atomCharInsertionIndex + charsToReplace)));
if (atomCharInsertionIndex + charsToReplace > atom
.getLength()) {
insertCharacterOffset = atomCharacterRange
.upperEndpoint();
charsToReplace -= (atom.getLength() - atomCharInsertionIndex);
} else {
done = true;
}
}
} else {
newAtoms.add(atom);
}
caretPosition += atom.getLength();
}
// Check for appending text to the end of the segment (no delete or
// replace)
if (caretPosition == insertCharacterOffset) {
newAtoms.add(new TextAtom(newText));
}
setAtoms(mergeNeighboringTextAtoms(newAtoms));
}
/**
* Prevent unnecessary SegmentAtom text fragmentation.
*/
private List<SegmentAtom> mergeNeighboringTextAtoms(
List<SegmentAtom> segmentAtoms) {
LinkedList<SegmentAtom> defraggedAtoms = new LinkedList<SegmentAtom>();
for (SegmentAtom atom : segmentAtoms) {
if (atom instanceof TextAtom && !defraggedAtoms.isEmpty()
&& defraggedAtoms.getLast() instanceof TextAtom) {
TextAtom mergedTextAtom = new TextAtom(defraggedAtoms.getLast()
.getData() + atom.getData());
defraggedAtoms.removeLast();
defraggedAtoms.add(mergedTextAtom);
} else {
defraggedAtoms.add(atom);
}
}
return defraggedAtoms;
}
private List<CodeAtom> findCodes(List<SegmentAtom> atoms) {
List<CodeAtom> codes = Lists.newArrayList();
for (SegmentAtom atom : atoms) {
if (atom instanceof CodeAtom) {
codes.add((CodeAtom) atom);
}
}
return codes;
}
public boolean isEnriched() {
return enriched;
}
public void clearHighlightedText(){
highlightDataList = null;
currentHighlightedIndex = -1;
}
public void setHighlightDataList(List<HighlightData> highlightDataList) {
this.highlightDataList = highlightDataList;
}
public List<HighlightData> getHighlightDataList(){
return highlightDataList;
}
public void addHighlightData(HighlightData highlightData){
if(highlightDataList == null){
highlightDataList = new ArrayList<HighlightData>();
}
highlightDataList.add(highlightData);
}
public void removeHighlightData(int atomIndex, int startIndex, int endIndex){
HighlightData hdToDelete = null;
if(highlightDataList != null){
for(HighlightData hd: highlightDataList){
if(hd.getAtomIndex() == atomIndex && hd.getHighlightIndices()[0] == startIndex && hd.getHighlightIndices()[1] == endIndex){
hdToDelete = hd;
break;
}
}
highlightDataList.remove(hdToDelete);
}
}
public void setCurrentHighlightedIndex(int currentHighlightedIndex){
this.currentHighlightedIndex = currentHighlightedIndex;
}
public int getCurrentHighlightedIndex(){
return currentHighlightedIndex;
}
public void setEnriched(final boolean enriched) {
this.enriched = enriched;
}
public void setSentToFreme(boolean sentToFreme){
this.sentToFreme = sentToFreme;
}
public boolean isSentToFreme(){
return sentToFreme;
}
public void replaced(String newString){
if (highlightDataList != null) {
HighlightData replacedHd = highlightDataList
.get(currentHighlightedIndex);
if (currentHighlightedIndex < highlightDataList.size() - 1) {
HighlightData nextHd = null;
int hdIndex = currentHighlightedIndex + 1;
int delta = newString.length()
- (replacedHd.getHighlightIndices()[1] - replacedHd
.getHighlightIndices()[0]);
while (hdIndex < highlightDataList.size()) {
nextHd = highlightDataList.get(hdIndex++);
if (replacedHd.getAtomIndex() == nextHd.getAtomIndex()) {
int[] newHLIndices = {
nextHd.getHighlightIndices()[0] + delta,
nextHd.getHighlightIndices()[1] + delta };
nextHd.setHighlightIndices(newHLIndices);
}
}
}
highlightDataList.remove(currentHighlightedIndex);
currentHighlightedIndex = -1;
}
}
public Set<Enrichment> getEnirchments() {
return enrichments;
}
public void setEnrichments(Set<Enrichment> enrichments) {
checkTranslation(enrichments);
enrichments.remove(transEnrichment);
this.enrichments = enrichments;
}
public void addEnrichment(final Enrichment enrichment){
if(enrichment != null){
if(enrichments == null){
enrichments = new HashSet<Enrichment>();
}
if(enrichment.getType().equals(Enrichment.TRANSLATION_TYPE)){
this.transEnrichment = (TranslationEnrichment) enrichment;
} else {
enrichments.add(enrichment);
}
}
}
public void addEnrichmentList(final List<Enrichment> enrichmentList){
if(enrichmentList != null){
if(enrichments == null){
enrichments = new HashSet<Enrichment>();
}
checkTranslation(enrichmentList);
enrichmentList.remove(transEnrichment);
enrichments.addAll(enrichmentList);
}
}
private void checkTranslation(final Collection<Enrichment> enrichmentList){
if (enrichmentList != null) {
for (Enrichment enrich : enrichmentList) {
if (enrich != null && enrich.getType().equals(
Enrichment.TRANSLATION_TYPE)) {
this.transEnrichment = (TranslationEnrichment) enrich;
break;
}
}
}
}
public void clearEnrichments(){
sentToFreme = false;
enriched = false;
enrichments = null;
transEnrichment = null;
}
public TranslationEnrichment getTranslationEnrichment(){
// TranslationEnrichment transEnrichment = null;
// if(enrichments != null){
// for(Enrichment enrich: enrichments){
// if(enrich.getType().equals("translation")) {
// transEnrichment = (TranslationEnrichment)enrich;
// break;
// }
// }
// }
return transEnrichment;
}
@Override
public String toString() {
return getDisplayText();
}
}