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

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

package org.fxmisc.richtext.model;

import org.reactfx.util.Tuple2;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * Helper class via {@link #constructDocument(SegmentOps, Object, Consumer)} or one of its constructors and
 * {@link #build()} for easily creating a {@link ReadOnlyStyledDocument} from a list
 * of {@link Paragraph}s that is constructed via "addParagraph(s)" methods.
 *
 * @param  the paragraph style type
 * @param  the segment type
 * @param  the segment style type
 */
public final class ReadOnlyStyledDocumentBuilder {

    /**
     * Constructs a list of paragraphs
     *
     * @param segmentOps the {@link SegmentOps} object to use for one of the {@link Paragraph}'s constructors
     * @param defaultParagraphStyle the paragraph style object to use when it is not specified in the
     *                              "addParagraph" methods
     * @param configuration call the builder's {@link #addParagraph(Object, Object)} methods here
     */
    public static  ReadOnlyStyledDocument constructDocument(
            SegmentOps segmentOps, PS defaultParagraphStyle,
            Consumer> configuration) {
        ReadOnlyStyledDocumentBuilder builder = new ReadOnlyStyledDocumentBuilder<>(segmentOps, defaultParagraphStyle);
        configuration.accept(builder);
        return builder.build();
    }

    /**
     * Constructs a list of paragraphs
     *
     * @param segmentOps the {@link SegmentOps} object to use for one of the {@link Paragraph}'s constructors
     * @param defaultParagraphStyle the paragraph style object to use when it is not specified in the
     *                              "addParagraph" methods
     * @param initialCapacity the initial capaicty for the underlying {@link ArrayList}
     * @param configuration call the builder's {@link #addParagraph(Object, Object)} methods here
     */
    public static  ReadOnlyStyledDocument constructDocument(
            SegmentOps segmentOps, PS defaultParagraphStyle,
            int initialCapacity, Consumer> configuration) {
        ReadOnlyStyledDocumentBuilder builder = new ReadOnlyStyledDocumentBuilder<>(segmentOps, defaultParagraphStyle, initialCapacity);
        configuration.accept(builder);
        return builder.build();
    }

    private final SegmentOps segmentOps;
    private final PS defaultParagraphStyle;
    private final List> paragraphList;

    private boolean alreadyCreated = false;

    /**
     * Creates a builder
     *
     * @param segmentOps the {@link SegmentOps} to use for each call to one of the {@link Paragraph}'s constructors.
     * @param defaultParagraphStyle the default paragraph style to use when one is not specified in the
     *                              "addParagraph"-prefixed methods
     */
    public ReadOnlyStyledDocumentBuilder(SegmentOps segmentOps, PS defaultParagraphStyle) {
        this(segmentOps, defaultParagraphStyle, new ArrayList<>());
    }

    /**
     * Creates a builder
     *
     * @param segmentOps the {@link SegmentOps} to use for each call to one of the {@link Paragraph}'s constructors.
     * @param defaultParagraphStyle the default paragraph style to use when one is not specified in the
     *                              "addParagraph"-prefixed methods
     * @param initialCapacity the initial capacity of the underlying {@link ArrayList}.
     */
    public ReadOnlyStyledDocumentBuilder(SegmentOps segmentOps, PS defaultParagraphStyle, int initialCapacity) {
        this(segmentOps, defaultParagraphStyle, new ArrayList<>(initialCapacity));
    }

    private ReadOnlyStyledDocumentBuilder(SegmentOps segmentOps, PS defaultParagraphStyle, List> list) {
        this.segmentOps = segmentOps;
        this.defaultParagraphStyle = defaultParagraphStyle;
        this.paragraphList = list;
    }

    /**
     * Adds to the list a paragraph that is constructed using the list of {@link StyledSegment}s.
     */
    public ReadOnlyStyledDocumentBuilder addParagraph(List> styledSegments) {
        return addParagraph(styledSegments, null);
    }

    /**
     * Adds to the list a paragraph that is constructed using the list of {@link StyledSegment}s.
     */
    public ReadOnlyStyledDocumentBuilder addParagraph(List> styledSegments, PS paragraphStyle) {
        return addPar(new Paragraph<>(argumentOrDefault(paragraphStyle), segmentOps, styledSegments));
    }

    /**
     * Adds to the list  a paragraph that has only one segment that has the same given style throughout.
     */
    public ReadOnlyStyledDocumentBuilder addParagraph(SEG segment, S style) {
        return addParagraph(segment, style, null);
    }

    /**
     * Adds to the list  a paragraph that has only one segment that has the same given style throughout.
     */
    public ReadOnlyStyledDocumentBuilder addParagraph(SEG segment, S style, PS paragraphStyle) {
        return addPar(new Paragraph<>(argumentOrDefault(paragraphStyle), segmentOps, segment, style));
    }

    /**
     * Adds to the list  a paragraph that has only one segment but a number of different styles throughout that segment
     */
    public ReadOnlyStyledDocumentBuilder addParagraph(SEG segment, StyleSpans styles) {
        return addParagraph(segment, styles, null);
    }

    /**
     * Adds to the list  a paragraph that has only one segment but a number of different styles throughout that segment
     */
    public ReadOnlyStyledDocumentBuilder addParagraph(SEG segment, StyleSpans styles, PS paragraphStyle) {
        return addPar(new Paragraph<>(argumentOrDefault(paragraphStyle), segmentOps, segment, styles));
    }

    /**
     * Adds to the list a paragraph that has multiple segments with multiple styles throughout those segments
     */
    public ReadOnlyStyledDocumentBuilder addParagraph(List segments, StyleSpans styles) {
        return addParagraph(segments, styles, null);
    }

    /**
     * Adds to the list a paragraph that has multiple segments with multiple styles throughout those segments
     */
    public ReadOnlyStyledDocumentBuilder addParagraph(List segments, StyleSpans styles, PS paragraphStyle) {
        return addPar(new Paragraph<>(argumentOrDefault(paragraphStyle), segmentOps, segments, styles));
    }

    /**
     * Adds multiple paragraphs to the list, using the {@link #defaultParagraphStyle} for each paragraph. For
     * more configuration on each paragraph's paragraph style, use {@link #addParagraphs0(List, StyleSpans)}
     *
     * @param listOfSegLists each item is the list of segments for a single paragraph
     * @param entireDocumentStyleSpans style spans for the entire document. It's length should be equal to the length
     *                                 of all the segments' length combined
     */
    public ReadOnlyStyledDocumentBuilder addParagraphs(List> listOfSegLists,
                                 StyleSpans entireDocumentStyleSpans) {
        return addParagraphList(listOfSegLists, entireDocumentStyleSpans, ignore -> null, Function.identity());
    }

    /**
     * Adds multiple paragraphs to the list, allowing one to specify each paragraph's paragraph style.
     *
     * @param paragraphArgList each item is a Tuple2 that represents the paragraph style and segment list
     *                         for a single paragraph. If the paragraph style is {@code null},
     *                         the {@link #defaultParagraphStyle} will be used instead.
     * @param entireDocumentStyleSpans style spans for the entire document. It's length should be equal to the length
     *                                 of all the segments' length combined
     */
    public ReadOnlyStyledDocumentBuilder addParagraphs0(List>> paragraphArgList,
                                 StyleSpans entireDocumentStyleSpans) {
        return addParagraphList(paragraphArgList, entireDocumentStyleSpans, Tuple2::get1, Tuple2::get2);
    }

    /**
     * Returns an unmodifiable list of the constructed {@link Paragraph}s and ensures this builder cannot be used again.
     */
    public ReadOnlyStyledDocument build() {
        ensureNotYetCreated();
        if (paragraphList.isEmpty()) {
            throw new IllegalStateException("Cannot build a ReadOnlyStyledDocument with an empty list of paragraphs!");
        }
        alreadyCreated = true;
        return new ReadOnlyStyledDocument<>(paragraphList);
    }

    private  ReadOnlyStyledDocumentBuilder addParagraphList(List paragraphContentList, StyleSpans spansThroughoutDocument,
                                       Function getStyle, Function> getSegList) {
        int docLength = paragraphContentList.stream()
                .map(getSegList)
                .flatMap(l -> l.stream().map(segmentOps::length))
                .reduce(0, (a, b) -> a + b);

        if (docLength != spansThroughoutDocument.length()) {
            throw new IllegalArgumentException(String.format(
                    "Document length does not equal style spans length! docLength=%s styleSpans' length=%s",
                    docLength, spansThroughoutDocument.length()
            ));
        }

        int styleOffset = 0;
        for (T paragraphContent : paragraphContentList) {
            PS paragraphStyle = argumentOrDefault(getStyle.apply(paragraphContent));
            List segList = getSegList.apply(paragraphContent);

            int paragraphLength = segList.stream().mapToInt(segmentOps::length).sum();
            int styleEnd = styleOffset + paragraphLength;
            StyleSpans spans = spansThroughoutDocument.subView(styleOffset, styleEnd);
            addPar(new Paragraph<>(paragraphStyle, segmentOps, segList, spans));

            styleOffset = styleEnd;
        }
        return this;
    }

    /**
     * Returns the argument if it is not null; otherwise, returns {@link #defaultParagraphStyle}
     */
    private PS argumentOrDefault(PS paragraphStyle) {
        return paragraphStyle != null ? paragraphStyle : defaultParagraphStyle;
    }

    private void ensureNotYetCreated() {
        if (alreadyCreated) {
            throw new IllegalStateException("This builder has already been used to create a list of Paragraphs. " +
                    "One builder can only be used to build a single list. To create a new one, create a new builder");
        }
    }

    private ReadOnlyStyledDocumentBuilder addPar(Paragraph paragraph) {
        ensureNotYetCreated();
        paragraphList.add(paragraph);
        return this;
    }
}