adding an annotated string
diff --git a/bundles/runtime/org.eclipse.fx.core/src/org/eclipse/fx/core/text/AnnotatedString.java b/bundles/runtime/org.eclipse.fx.core/src/org/eclipse/fx/core/text/AnnotatedString.java
new file mode 100644
index 0000000..90c1be8
--- /dev/null
+++ b/bundles/runtime/org.eclipse.fx.core/src/org/eclipse/fx/core/text/AnnotatedString.java
@@ -0,0 +1,282 @@
+/*******************************************************************************
+ * Copyright (c) 2017 BestSolution.at and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Tom Schindl <tom.schindl@bestsolution.at> - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.fx.core.text;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import org.eclipse.fx.core.Range;
+import org.eclipse.fx.core.array.ArrayUtils;
+
+/**
+ * {@link CharSequence} who is built from annotated segments
+ *
+ * @since 3.0
+ */
+public final class AnnotatedString implements CharSequence {
+ final char[] content;
+ final int[][] ranges;
+ final String[][] annotations;
+
+ /**
+ * Consumes a segment
+ */
+ public interface SegmentConsumer {
+ /**
+ * Consumes a segment
+ *
+ * @param start
+ * the start index (inclusive)
+ * @param end
+ * the end index (exclusive)
+ * @param annotation
+ * the annotations
+ */
+ public void consume(int start, int end, String[] annotation);
+ }
+
+ /**
+ * A segment of the {@link AnnotatedString}
+ */
+ public class Segment {
+ private final int idx;
+
+ Segment(int idx) {
+ this.idx = idx;
+ }
+
+ /**
+ * @return the text in the segment
+ */
+ public char[] text() {
+ return Arrays.copyOfRange(AnnotatedString.this.content, start(), end());
+ }
+
+ /**
+ * @return stream of characters
+ */
+ public IntStream chars() {
+ return ArrayUtils.stream(AnnotatedString.this.content, start(), end());
+ }
+
+ /**
+ * @return the start index of the segment (inclusive)
+ */
+ public int start() {
+ return AnnotatedString.this.ranges[this.idx][0];
+ }
+
+ /**
+ * @return the end index of the segment (exclusive)
+ */
+ public int end() {
+ return AnnotatedString.this.ranges[this.idx][1];
+ }
+
+ /**
+ * @return annotations
+ */
+ public String[] annotations() {
+ return Arrays.copyOf(AnnotatedString.this.annotations[this.idx],
+ AnnotatedString.this.annotations[this.idx].length);
+ }
+ }
+
+ AnnotatedString(char[] content, int[][] ranges, String[][] annotations) {
+ this.content = content;
+ this.ranges = ranges;
+ this.annotations = annotations;
+ }
+
+ /**
+ * Apply the consumer on each segment
+ *
+ * @param consumer
+ * the consumer
+ */
+ public void forEachSegment(SegmentConsumer consumer) {
+ for (int r = 0; r < this.ranges.length; r++) {
+ consumer.consume(this.ranges[r][0], this.ranges[r][1], this.annotations[r]);
+ }
+ }
+
+ /**
+ * @return stream of segments
+ */
+ public Stream<Segment> stream() {
+ // Iterator<Segment> it = new Iterator<Segment>() {
+ // private int cur = 0;
+ //
+ // @Override
+ // public Segment next() {
+ // if( hasNext() ) {
+ // return new Segment(cur++);
+ // } else {
+ // throw new NoSuchElementException();
+ // }
+ //
+ // }
+ //
+ // @Override
+ // public boolean hasNext() {
+ // return this.cur < AnnotatedString.this.ranges.length;
+ // }
+ // };
+ // return StreamSupport.stream(() ->
+ // Spliterators.spliterator(it, this.ranges.length,Spliterator.ORDERED),
+ // Spliterator.SUBSIZED | Spliterator.SIZED | Spliterator.ORDERED,
+ // false);
+ return IntStream.range(0, this.ranges.length).mapToObj(Segment::new);
+
+ }
+
+ /**
+ * Get the content for the provided index
+ *
+ * @param segmentIndex
+ * the index
+ * @return the content
+ */
+ public char[] getContent(int segmentIndex) {
+ return Arrays.copyOfRange(this.content, this.ranges[segmentIndex][0], this.ranges[segmentIndex][1]);
+ }
+
+ /**
+ * Copy the content to the provided character array
+ *
+ * @param segmentIndex
+ * the segment index
+ * @param target
+ * the target array
+ * @return the number of chars copied to the target
+ */
+ public int copyContent(int segmentIndex, char[] target) {
+ int l = this.ranges[segmentIndex][1] - this.ranges[segmentIndex][0];
+ System.arraycopy(this.content, 0, target, 0, l);
+ return l;
+ }
+
+ @Override
+ public int length() {
+ return this.content.length;
+ }
+
+ @Override
+ public char charAt(int index) {
+ return this.content[index];
+ }
+
+ @Override
+ public CharSequence subSequence(int start, int end) {
+ int idx = -1;
+ for (int i = 0; i < this.ranges.length; i++) {
+ if (Range.intersects(start, end, this.ranges[i][0], this.ranges[i][1])) {
+ idx = i;
+ break;
+ }
+ }
+
+ int[][] targetRange = new int[0][0];
+ String[][] targetAnnotation = new String[0][0];
+ if (idx != -1) {
+ targetRange = new int[idx + 1][2];
+ targetAnnotation = new String[idx + 1][0];
+ }
+
+ return new AnnotatedString(Arrays.copyOfRange(this.content, start, end), targetRange, targetAnnotation);
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(this.content);
+ }
+
+ /**
+ * Create a builder to create an {@link AnnotatedString}
+ *
+ * @param capacity
+ * the initial capacity
+ * @return the builder
+ */
+ public static Builder create(int capacity) {
+ return new Builder(capacity);
+ }
+
+ private static class Struct {
+ int[] range;
+ String[] annotations;
+
+ public Struct(int start, int end, String[] annotations) {
+ this.range = new int[] { start, end };
+ this.annotations = annotations;
+ }
+ }
+
+ /**
+ * Builder used to create an {@link AnnotatedString}
+ */
+ public final static class Builder {
+ private StringBuilder builder;
+ private final List<Struct> ranges = new ArrayList<>();
+
+ Builder(int capacity) {
+ this.builder = new StringBuilder(capacity);
+ }
+
+ /**
+ * Add the provided the character array
+ *
+ * @param c
+ * the character array
+ * @param annotations
+ * the annotations
+ * @return the builder
+ */
+ public Builder add(char[] c, String... annotations) {
+ this.ranges.add(new Struct(this.builder.length(), this.builder.length() + c.length, annotations));
+ this.builder.append(c);
+ return this;
+ }
+
+ /**
+ * Add the provided {@link CharSequence}
+ *
+ * @param s
+ * the sequence
+ * @param annotations
+ * the annotations
+ * @return the builder
+ */
+ public Builder add(CharSequence s, String... annotations) {
+ this.ranges.add(new Struct(this.builder.length(), this.builder.length() + s.length(), annotations));
+ this.builder.append(s);
+ return this;
+ }
+
+ /**
+ * @return an {@link AnnotatedString} instance
+ */
+ public AnnotatedString build() {
+ char[] content = new char[this.builder.length()];
+ this.builder.getChars(0, this.builder.length(), content, 0);
+
+ int[][] ranges = new int[this.ranges.size()][2];
+ String[][] annotations = new String[this.ranges.size()][0];
+ ArrayUtils.fill(ranges, i -> this.ranges.get(i).range);
+ ArrayUtils.fill(annotations, i -> this.ranges.get(i).annotations);
+
+ return new AnnotatedString(content, ranges, annotations);
+ }
+ }
+}