All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fxmisc.richtext.model.StyleSpansBuilder Maven / Gradle / Ivy

There is a newer version: 0.11.3
Show newest version
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 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> styleSpans) {
        return addAll(styleSpans, styleSpans.size());
    }

    public StyleSpansBuilder addAll(Iterable> styleSpans, int sizeHint) {
        spans.ensureCapacity(spans.size() + sizeHint);
        return addAll(styleSpans);
    }

    public StyleSpansBuilder addAll(Iterable> styleSpans) {
        ensureNotCreated();
        for(StyleSpan span: styleSpans) {
            _add(span);
        }
        return this;
    }

    public StyleSpansBuilder addAll(Iterator> 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));
        }
    }
}