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

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

package org.fxmisc.richtext.model;

import java.util.Optional;
import java.util.function.BiFunction;

import org.reactfx.util.Either;

/**
 * Defines the operations which are supported on a specific segment type.
 *
 * @param  The segment type
 * @param  The style type for the segment
 */
public interface SegmentOps {

    public int length(SEG seg);

    public char charAt(SEG seg, int index);

    public String getText(SEG seg);

    public SEG subSequence(SEG seg, int start, int end);

    public SEG subSequence(SEG seg, int start);

    /**
     * Joins two consecutive segments together into one or {@link Optional#empty()} if they cannot be joined.
     */
    public Optional joinSeg(SEG currentSeg, SEG nextSeg);

    /**
     * Joins two consecutive styles together into one or {@link Optional#empty()} if they cannot be joined. By default,
     * returns {@link Optional#empty()}.
     */
    default Optional joinStyle(S currentStyle, S nextStyle) {
        return Optional.empty();
    }

    /**
     * Creates an empty segment. This method should return the same object for better performance and memory usage.
     */
    public SEG createEmptySeg();

    /**
     * Creates a {@link TextOps} specified for a {@link String} segment that never merges consecutive styles
     */
    public static  TextOps styledTextOps() {
        return styledTextOps((s1, s2) -> Optional.empty());
    }

    /**
     * Creates a {@link TextOps} specified for a {@link String}
     */
    public static  TextOps styledTextOps(BiFunction> mergeStyle) {
        return new TextOpsBase("") {
            @Override
            public char realCharAt(String s, int index) {
                return s.charAt(index);
            }

            @Override
            public String realGetText(String s) {
                return s;
            }

            @Override
            public String realSubSequence(String s, int start, int end) {
                return s.substring(start, end);
            }

            @Override
            public String create(String text) {
                return text;
            }

            @Override
            public int length(String s) {
                return s.length();
            }

            @Override
            public Optional joinSeg(String currentSeg, String nextSeg) {
                return Optional.of(currentSeg + nextSeg);
            }

            @Override
            public Optional joinStyle(S currentStyle, S nextStyle) {
                return mergeStyle.apply(currentStyle, nextStyle);
            }
        };
    }

    /**
     * Returns a {@link SegmentOps} that specifies its segment type to be an {@link Either}
     * whose {@link Either#left(Object) left} value is this segment type and
     * whose {@link Either#right(Object) right} value is {@code rOps}' segment type.
     */
    public default  SegmentOps, S> or(SegmentOps rOps) {
        return either(this, rOps);
    }

    /**
     * Returns a {@link SegmentOps}
     *  that specifies its segment type to be an {@link Either}
     *      whose {@link Either#left(Object) left} value is this segment type and
     *      whose {@link Either#right(Object) right} value is {@code rOps}' segment type, and
     *  that specifies its style type to be {@link Either}
     *      whose {@link Either#left(Object) left} value is this style type and
     *      whose {@link Either#right(Object) right} value is {@code rOps}' style type.
     */
    public default  SegmentOps, Either> orStyled(
            SegmentOps rOps
    ) {
        return eitherStyles(this, rOps);
    }

    /**
     * Returns a {@link SegmentOps}
     *  that specifies its segment type to be an {@link Either}
     *      whose {@link Either#left(Object) left} value is {@code lOps}' segment type and
     *      whose {@link Either#right(Object) right} value is {@code rOps}' segment type, and
     *  that specifies its style type to be {@link Either}
     *      whose {@link Either#left(Object) left} value is {@code lOps}' style type and
     *      whose {@link Either#right(Object) right} value is {@code rOps}' style type.
     *
     * Note: consecutive styles will not be merged.
     */
    public static  SegmentOps, Either> eitherStyles(
            SegmentOps lOps,
            SegmentOps rOps) {
        return new EitherStyledSegmentOps<>(lOps, rOps);
    }

    /**
     * Returns a {@link SegmentOps} that specifies its segment type to be an {@link Either}
     * whose {@link Either#left(Object) left} value is {@code lOps}' segment type and
     * whose {@link Either#right(Object) right} value is {@code rOps}' segment type.
     *
     * Note: consecutive styles will not be merged.
     */
    public static  SegmentOps, Style> either(SegmentOps lOps,
                                                                                   SegmentOps rOps) {
        return either(lOps, rOps, (leftStyle, rightStyle) -> Optional.empty());
    }

    /**
     * Returns a {@link SegmentOps} that specifies its segment type to be an {@link Either}
     * whose {@link Either#left(Object) left} value is {@code lOps}' segment type and
     * whose {@link Either#right(Object) right} value is {@code rOps}' segment type.
     */
    public static  SegmentOps, Style> either(
            SegmentOps lOps, SegmentOps rOps,
            BiFunction> mergeStyle) {
        return new EitherSegmentOps<>(lOps, rOps, mergeStyle);
    }
}

class EitherSegmentOps implements SegmentOps, S> {

    private final SegmentOps lOps;
    private final SegmentOps rOps;
    private final BiFunction> mergeStyle;

    EitherSegmentOps(SegmentOps lOps, SegmentOps rOps, BiFunction> mergeStyle) {
        this.lOps = lOps;
        this.rOps = rOps;
        this.mergeStyle = mergeStyle;
    }


    @Override
    public int length(Either seg) {
        return seg.unify(lOps::length, rOps::length);
    }

    @Override
    public char charAt(Either seg, int index) {
        return seg.unify(l -> lOps.charAt(l, index),
                         r -> rOps.charAt(r, index));
    }

    @Override
    public String getText(Either seg) {
        return seg.unify(lOps::getText, rOps::getText);
    }

    @Override
    public Either subSequence(Either seg, int start, int end) {
        return seg.map(l -> lOps.subSequence(l, start, end),
                       r -> rOps.subSequence(r, start, end));
    }

    @Override
    public Either subSequence(Either seg, int start) {
        return seg.map(l -> lOps.subSequence(l, start),
                       r -> rOps.subSequence(r, start));
    }

    @Override
    public Optional> joinSeg(Either left, Either right) {
        return left.unify(ll -> right.unify(rl -> lOps.joinSeg(ll, rl).map(Either::left), rr -> Optional.empty()),
                          lr -> right.unify(rl -> Optional.empty(), rr -> rOps.joinSeg(lr, rr).map(Either::right)));
    }

    @Override
    public Optional joinStyle(S currentStyle, S nextStyle) {
        return mergeStyle.apply(currentStyle, nextStyle);
    }

    public Either createEmptySeg() {
        return Either.left(lOps.createEmptySeg());
    }
}

class EitherStyledSegmentOps implements SegmentOps, Either> {

    private final SegmentOps lOps;
    private final SegmentOps rOps;

    EitherStyledSegmentOps(SegmentOps lOps, SegmentOps rOps) {
        this.lOps = lOps;
        this.rOps = rOps;
    }


    @Override
    public int length(Either seg) {
        return seg.unify(
                lOps::length,
                rOps::length
        );
    }

    @Override
    public char charAt(Either seg, int index) {
        return seg.unify(
                lSeg -> lOps.charAt(lSeg, index),
                rSeg -> rOps.charAt(rSeg, index)
        );
    }

    @Override
    public String getText(Either seg) {
        return seg.unify(lOps::getText, rOps::getText);
    }

    @Override
    public Either subSequence(Either seg, int start, int end) {
        return seg.map(
                lSeg -> lOps.subSequence(lSeg, start, end),
                rSeg -> rOps.subSequence(rSeg, start, end)
        );
    }

    @Override
    public Either subSequence(Either seg, int start) {
        return seg.map(lSeg -> lOps.subSequence(lSeg, start),
                rSeg -> rOps.subSequence(rSeg, start));
    }

    @Override
    public Optional> joinSeg(Either left, Either right) {
        return left.unify(
                ll -> right.unify(
                        rl -> lOps.joinSeg(ll, rl).map(Either::left),
                        rr -> Optional.empty()),
                lr -> right.unify(
                        rl -> Optional.empty(),
                        rr -> rOps.joinSeg(lr, rr).map(Either::right))
        );
    }

    @Override
    public Optional> joinStyle(Either left, Either right) {
        return left.unify(
                ll -> right.unify(
                        rl -> lOps.joinStyle(ll, rl).map(Either::left),
                        rr -> Optional.empty()),
                lr -> right.unify(
                        rl -> Optional.empty(),
                        rr -> rOps.joinStyle(lr, rr).map(Either::right))
        );
    }

    public Either createEmptySeg() {
        return Either.left(lOps.createEmptySeg());
    }
}