org.fxmisc.richtext.model.StyleSpansBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of richtextfx Show documentation
Show all versions of richtextfx Show documentation
Rich-text area for JavaFX
package org.fxmisc.richtext.model;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.function.BiFunction;
/**
* A one-time-use builder that Builds a memory efficient {@link StyleSpans} object.
*
* @param the segment style type
*/
public class StyleSpansBuilder {
private static class StyleSpansImpl extends StyleSpansBase {
private final List> spans;
private int length = -1;
StyleSpansImpl(List> spans) {
this.spans = spans;
}
@Override
public Iterator> iterator() {
return spans.iterator();
}
@Override
public int length() {
if(length == -1) {
length = spans.stream().mapToInt(StyleSpan::getLength).sum();
}
return length;
}
@Override
public int getSpanCount() {
return spans.size();
}
@Override
public StyleSpan getStyleSpan(int index) {
return spans.get(index);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("StyleSpans(length=").append(length())
.append(" spanCount=").append(getSpanCount())
.append(" spans=").append(spans)
.append(")");
return sb.toString();
}
}
static StyleSpans overlay(
StyleSpans s1,
StyleSpans s2,
BiFunction super S, ? super S, ? extends S> f) {
StyleSpansBuilder acc = new StyleSpansBuilder<>(s1.getSpanCount() + s2.getSpanCount());
Iterator> t1 = s1.iterator();
Iterator> t2 = s2.iterator();
StyleSpan h1 = t1.next(); // remember that all StyleSpans have at least one StyleSpan
StyleSpan h2 = t2.next(); // remember that all StyleSpans have at least one StyleSpan
while(true) {
int len1 = h1.getLength();
int len2 = h2.getLength();
if(len1 == len2) {
acc.add(f.apply(h1.getStyle(), h2.getStyle()), len1);
if(!t1.hasNext()) {
return acc.addAll(t2).create();
} else if(!t2.hasNext()) {
return acc.addAll(t1).create();
} else {
h1 = t1.next();
h2 = t2.next();
}
} else if(len1 < len2) {
acc.add(f.apply(h1.getStyle(), h2.getStyle()), len1);
h2 = new StyleSpan<>(h2.getStyle(), len2 - len1);
if(t1.hasNext()) {
h1 = t1.next();
} else {
return acc.add(h2).addAll(t2).create();
}
} else { // len1 > len2
acc.add(f.apply(h1.getStyle(), h2.getStyle()), len2);
h1 = new StyleSpan<>(h1.getStyle(), len1 - len2);
if(t2.hasNext()) {
h2 = t2.next();
} else {
return acc.add(h1).addAll(t1).create();
}
}
}
}
private boolean created = false;
private final ArrayList> spans;
public StyleSpansBuilder(int initialCapacity) {
this.spans = new ArrayList<>(initialCapacity);
}
public StyleSpansBuilder() {
this.spans = new ArrayList<>();
}
public StyleSpansBuilder add(StyleSpan styleSpan) {
ensureNotCreated();
_add(styleSpan);
return this;
}
public StyleSpansBuilder add(S style, int length) {
return add(new StyleSpan<>(style, length));
}
public StyleSpansBuilder addAll(Collection extends StyleSpan> styleSpans) {
return addAll(styleSpans, styleSpans.size());
}
public StyleSpansBuilder addAll(Iterable extends StyleSpan> styleSpans, int sizeHint) {
spans.ensureCapacity(spans.size() + sizeHint);
return addAll(styleSpans);
}
public StyleSpansBuilder addAll(Iterable extends StyleSpan> styleSpans) {
ensureNotCreated();
for(StyleSpan span: styleSpans) {
_add(span);
}
return this;
}
public StyleSpansBuilder addAll(Iterator extends StyleSpan> styleSpans) {
ensureNotCreated();
while(styleSpans.hasNext()) {
_add(styleSpans.next());
}
return this;
}
public StyleSpans create() {
ensureNotCreated();
if(spans.isEmpty()) {
throw new IllegalStateException("No spans have been added");
}
created = true;
return new StyleSpansImpl<>(Collections.unmodifiableList(spans));
}
private void _add(StyleSpan span) {
if(spans.isEmpty()) {
spans.add(span);
} else if(span.getLength() > 0) {
if(spans.size() == 1 && spans.get(0).getLength() == 0) {
spans.set(0, span);
} else {
StyleSpan prev = spans.get(spans.size() - 1);
if(prev.getStyle().equals(span.getStyle())) {
spans.set(spans.size() - 1, new StyleSpan<>(span.getStyle(), prev.getLength() + span.getLength()));
} else {
spans.add(span);
}
}
} else {
// do nothing, don't add a zero-length span
}
}
private void ensureNotCreated() {
if(created) {
throw new IllegalStateException("Cannot reuse StyleRangesBuilder after StyleRanges have been created.");
}
}
}
abstract class StyleSpansBase implements StyleSpans {
protected final TwoLevelNavigator navigator = new TwoLevelNavigator(
this::getSpanCount,
i -> getStyleSpan(i).getLength());
@Override
public Position position(int major, int minor) {
return navigator.position(major, minor);
}
@Override
public Position offsetToPosition(int offset, Bias bias) {
return navigator.offsetToPosition(offset, bias);
}
@Override
public boolean equals(Object other) {
if(other instanceof StyleSpans) {
StyleSpans> that = (StyleSpans>) other;
if(this.getSpanCount() != that.getSpanCount()) {
return false;
}
for(int i = 0; i < this.getSpanCount(); ++i) {
if(!this.getStyleSpan(i).equals(that.getStyleSpan(i))) {
return false;
}
}
return true;
} else {
return false;
}
}
@Override
public int hashCode() {
int result = 1;
for(StyleSpan span: this) {
result = 31 * result + span.hashCode();
}
return result;
}
}
class SubSpans extends StyleSpansBase {
private final StyleSpans original;
private final int firstIdxInOrig;
private final int spanCount;
private final StyleSpan firstSpan;
private final StyleSpan lastSpan;
int length = -1;
public SubSpans(StyleSpans original, Position from, Position to) {
this.original = original;
this.firstIdxInOrig = from.getMajor();
this.spanCount = to.getMajor() - from.getMajor() + 1;
if(spanCount == 1) {
StyleSpan span = original.getStyleSpan(firstIdxInOrig);
int len = to.getMinor() - from.getMinor();
firstSpan = lastSpan = new StyleSpan<>(span.getStyle(), len);
} else {
StyleSpan startSpan = original.getStyleSpan(firstIdxInOrig);
int len = startSpan.getLength() - from.getMinor();
firstSpan = new StyleSpan<>(startSpan.getStyle(), len);
StyleSpan endSpan = original.getStyleSpan(to.getMajor());
lastSpan = new StyleSpan<>(endSpan.getStyle(), to.getMinor());
}
}
@Override
public int length() {
if(length == -1) {
length = 0;
for(StyleSpan span: this) {
length += span.getLength();
}
}
return length;
}
@Override
public int getSpanCount() {
return spanCount;
}
@Override
public StyleSpan getStyleSpan(int index) {
if(index == 0) {
return firstSpan;
} else if(index == spanCount - 1) {
return lastSpan;
} else if(index < 0 || index >= spanCount) {
throw new IndexOutOfBoundsException(String.valueOf(index));
} else {
return original.getStyleSpan(firstIdxInOrig + index);
}
}
@Override
public String toString() {
ArrayList> spans = new ArrayList<>(spanCount);
for (int i = 0; i < spanCount; i++) {
spans.add(getStyleSpan(i));
}
StringBuilder sb = new StringBuilder();
sb.append("SubSpans(length=").append(length)
.append(" spanCount=").append(getSpanCount())
.append(" spans=").append(spans)
.append(")");
return sb.toString();
}
}
class AppendedSpans extends StyleSpansBase {
private final StyleSpans original;
private final StyleSpan appended;
private int length = -1;
private int spanCount = -1;
public AppendedSpans(StyleSpans original, StyleSpan appended) {
this.original = original;
this.appended = appended;
}
@Override
public int length() {
if(length == -1) {
length = original.length() + appended.getLength();
}
return length;
}
@Override
public int getSpanCount() {
if(spanCount == -1) {
spanCount = original.getSpanCount() + 1;
}
return spanCount;
}
@Override
public StyleSpan getStyleSpan(int index) {
if(index == getSpanCount() - 1) {
return appended;
} else {
return original.getStyleSpan(index);
}
}
}
class PrependedSpans extends StyleSpansBase {
private final StyleSpans original;
private final StyleSpan prepended;
private int length = -1;
private int spanCount = -1;
public PrependedSpans(StyleSpans original, StyleSpan prepended) {
this.original = original;
this.prepended = prepended;
}
@Override
public int length() {
if(length == -1) {
length = prepended.getLength() + original.length();
}
return length;
}
@Override
public int getSpanCount() {
if(spanCount == -1) {
spanCount = 1 + original.getSpanCount();
}
return spanCount;
}
@Override
public StyleSpan getStyleSpan(int index) {
if(index == 0) {
return prepended;
} else {
return original.getStyleSpan(index - 1);
}
}
}
class UpdatedSpans extends StyleSpansBase {
private final StyleSpans original;
private final int index;
private final StyleSpan update;
private int length = -1;
public UpdatedSpans(StyleSpans original, int index, StyleSpan update) {
this.original = original;
this.index = index;
this.update = update;
}
@Override
public int length() {
if(length == -1) {
length = original.length() - original.getStyleSpan(index).getLength() + update.getLength();
}
return length;
}
@Override
public int getSpanCount() {
return original.getSpanCount();
}
@Override
public StyleSpan getStyleSpan(int index) {
if(index == this.index) {
return update;
} else {
return original.getStyleSpan(index);
}
}
}
class SingletonSpans extends StyleSpansBase {
private final StyleSpan span;
public SingletonSpans(StyleSpan span) {
this.span = span;
}
@Override
public int length() {
return span.getLength();
}
@Override
public int getSpanCount() {
return 1;
}
@Override
public StyleSpan getStyleSpan(int index) {
if(index == 0) {
return span;
} else {
throw new IndexOutOfBoundsException(String.valueOf(index));
}
}
}