1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 * License for the specific language governing permissions and limitations under
15 * the License.
16 */
17
18package java.text;
19
20import java.text.AttributedCharacterIterator.Attribute;
21import java.util.ArrayList;
22import java.util.Arrays;
23import java.util.HashMap;
24import java.util.HashSet;
25import java.util.Iterator;
26import java.util.List;
27import java.util.ListIterator;
28import java.util.Map;
29import java.util.Set;
30
31/**
32 * Holds a string with attributes describing the characters of
33 * this string.
34 */
35public class AttributedString {
36
37    String text;
38
39    Map<AttributedCharacterIterator.Attribute, List<Range>> attributeMap;
40
41    static class Range {
42        int start;
43
44        int end;
45
46        Object value;
47
48        Range(int s, int e, Object v) {
49            start = s;
50            end = e;
51            value = v;
52        }
53    }
54
55    static class AttributedIterator implements AttributedCharacterIterator {
56
57        private int begin, end, offset;
58
59        private AttributedString attrString;
60
61        private HashSet<Attribute> attributesAllowed;
62
63        AttributedIterator(AttributedString attrString) {
64            this.attrString = attrString;
65            begin = 0;
66            end = attrString.text.length();
67            offset = 0;
68        }
69
70        AttributedIterator(AttributedString attrString,
71                AttributedCharacterIterator.Attribute[] attributes, int begin,
72                int end) {
73            if (begin < 0 || end > attrString.text.length() || begin > end) {
74                throw new IllegalArgumentException();
75            }
76            this.begin = begin;
77            this.end = end;
78            offset = begin;
79            this.attrString = attrString;
80            if (attributes != null) {
81                HashSet<Attribute> set = new HashSet<Attribute>(
82                        (attributes.length * 4 / 3) + 1);
83                for (int i = attributes.length; --i >= 0;) {
84                    set.add(attributes[i]);
85                }
86                attributesAllowed = set;
87            }
88        }
89
90        /**
91         * Returns a new {@code AttributedIterator} with the same source string,
92         * begin, end, and current index as this attributed iterator.
93         *
94         * @return a shallow copy of this attributed iterator.
95         * @see java.lang.Cloneable
96         */
97        @Override
98        @SuppressWarnings("unchecked")
99        public Object clone() {
100            try {
101                AttributedIterator clone = (AttributedIterator) super.clone();
102                if (attributesAllowed != null) {
103                    clone.attributesAllowed = (HashSet<Attribute>) attributesAllowed
104                            .clone();
105                }
106                return clone;
107            } catch (CloneNotSupportedException e) {
108                throw new AssertionError(e);
109            }
110        }
111
112        public char current() {
113            if (offset == end) {
114                return DONE;
115            }
116            return attrString.text.charAt(offset);
117        }
118
119        public char first() {
120            if (begin == end) {
121                return DONE;
122            }
123            offset = begin;
124            return attrString.text.charAt(offset);
125        }
126
127        /**
128         * Returns the begin index in the source string.
129         *
130         * @return the index of the first character to iterate.
131         */
132        public int getBeginIndex() {
133            return begin;
134        }
135
136        /**
137         * Returns the end index in the source String.
138         *
139         * @return the index one past the last character to iterate.
140         */
141        public int getEndIndex() {
142            return end;
143        }
144
145        /**
146         * Returns the current index in the source String.
147         *
148         * @return the current index.
149         */
150        public int getIndex() {
151            return offset;
152        }
153
154        private boolean inRange(Range range) {
155            if (!(range.value instanceof Annotation)) {
156                return true;
157            }
158            return range.start >= begin && range.start < end
159                    && range.end > begin && range.end <= end;
160        }
161
162        private boolean inRange(List<Range> ranges) {
163            Iterator<Range> it = ranges.iterator();
164            while (it.hasNext()) {
165                Range range = it.next();
166                if (range.start >= begin && range.start < end) {
167                    return !(range.value instanceof Annotation)
168                            || (range.end > begin && range.end <= end);
169                } else if (range.end > begin && range.end <= end) {
170                    return !(range.value instanceof Annotation)
171                            || (range.start >= begin && range.start < end);
172                }
173            }
174            return false;
175        }
176
177        /**
178         * Returns a set of attributes present in the {@code AttributedString}.
179         * An empty set returned indicates that no attributes where defined.
180         *
181         * @return a set of attribute keys that may be empty.
182         */
183        public Set<AttributedIterator.Attribute> getAllAttributeKeys() {
184            if (begin == 0 && end == attrString.text.length()
185                    && attributesAllowed == null) {
186                return attrString.attributeMap.keySet();
187            }
188
189            Set<AttributedIterator.Attribute> result = new HashSet<Attribute>(
190                    (attrString.attributeMap.size() * 4 / 3) + 1);
191            Iterator<Map.Entry<Attribute, List<Range>>> it = attrString.attributeMap
192                    .entrySet().iterator();
193            while (it.hasNext()) {
194                Map.Entry<Attribute, List<Range>> entry = it.next();
195                if (attributesAllowed == null
196                        || attributesAllowed.contains(entry.getKey())) {
197                    List<Range> ranges = entry.getValue();
198                    if (inRange(ranges)) {
199                        result.add(entry.getKey());
200                    }
201                }
202            }
203            return result;
204        }
205
206        private Object currentValue(List<Range> ranges) {
207            Iterator<Range> it = ranges.iterator();
208            while (it.hasNext()) {
209                Range range = it.next();
210                if (offset >= range.start && offset < range.end) {
211                    return inRange(range) ? range.value : null;
212                }
213            }
214            return null;
215        }
216
217        public Object getAttribute(
218                AttributedCharacterIterator.Attribute attribute) {
219            if (attributesAllowed != null
220                    && !attributesAllowed.contains(attribute)) {
221                return null;
222            }
223            ArrayList<Range> ranges = (ArrayList<Range>) attrString.attributeMap
224                    .get(attribute);
225            if (ranges == null) {
226                return null;
227            }
228            return currentValue(ranges);
229        }
230
231        public Map<Attribute, Object> getAttributes() {
232            Map<Attribute, Object> result = new HashMap<Attribute, Object>(
233                    (attrString.attributeMap.size() * 4 / 3) + 1);
234            Iterator<Map.Entry<Attribute, List<Range>>> it = attrString.attributeMap
235                    .entrySet().iterator();
236            while (it.hasNext()) {
237                Map.Entry<Attribute, List<Range>> entry = it.next();
238                if (attributesAllowed == null
239                        || attributesAllowed.contains(entry.getKey())) {
240                    Object value = currentValue(entry.getValue());
241                    if (value != null) {
242                        result.put(entry.getKey(), value);
243                    }
244                }
245            }
246            return result;
247        }
248
249        public int getRunLimit() {
250            return getRunLimit(getAllAttributeKeys());
251        }
252
253        private int runLimit(List<Range> ranges) {
254            int result = end;
255            ListIterator<Range> it = ranges.listIterator(ranges.size());
256            while (it.hasPrevious()) {
257                Range range = it.previous();
258                if (range.end <= begin) {
259                    break;
260                }
261                if (offset >= range.start && offset < range.end) {
262                    return inRange(range) ? range.end : result;
263                } else if (offset >= range.end) {
264                    break;
265                }
266                result = range.start;
267            }
268            return result;
269        }
270
271        public int getRunLimit(AttributedCharacterIterator.Attribute attribute) {
272            if (attributesAllowed != null
273                    && !attributesAllowed.contains(attribute)) {
274                return end;
275            }
276            ArrayList<Range> ranges = (ArrayList<Range>) attrString.attributeMap
277                    .get(attribute);
278            if (ranges == null) {
279                return end;
280            }
281            return runLimit(ranges);
282        }
283
284        public int getRunLimit(Set<? extends Attribute> attributes) {
285            int limit = end;
286            Iterator<? extends Attribute> it = attributes.iterator();
287            while (it.hasNext()) {
288                AttributedCharacterIterator.Attribute attribute = it.next();
289                int newLimit = getRunLimit(attribute);
290                if (newLimit < limit) {
291                    limit = newLimit;
292                }
293            }
294            return limit;
295        }
296
297        public int getRunStart() {
298            return getRunStart(getAllAttributeKeys());
299        }
300
301        private int runStart(List<Range> ranges) {
302            int result = begin;
303            Iterator<Range> it = ranges.iterator();
304            while (it.hasNext()) {
305                Range range = it.next();
306                if (range.start >= end) {
307                    break;
308                }
309                if (offset >= range.start && offset < range.end) {
310                    return inRange(range) ? range.start : result;
311                } else if (offset < range.start) {
312                    break;
313                }
314                result = range.end;
315            }
316            return result;
317        }
318
319        public int getRunStart(AttributedCharacterIterator.Attribute attribute) {
320            if (attributesAllowed != null
321                    && !attributesAllowed.contains(attribute)) {
322                return begin;
323            }
324            ArrayList<Range> ranges = (ArrayList<Range>) attrString.attributeMap
325                    .get(attribute);
326            if (ranges == null) {
327                return begin;
328            }
329            return runStart(ranges);
330        }
331
332        public int getRunStart(Set<? extends Attribute> attributes) {
333            int start = begin;
334            Iterator<? extends Attribute> it = attributes.iterator();
335            while (it.hasNext()) {
336                AttributedCharacterIterator.Attribute attribute = it.next();
337                int newStart = getRunStart(attribute);
338                if (newStart > start) {
339                    start = newStart;
340                }
341            }
342            return start;
343        }
344
345        public char last() {
346            if (begin == end) {
347                return DONE;
348            }
349            offset = end - 1;
350            return attrString.text.charAt(offset);
351        }
352
353        public char next() {
354            if (offset >= (end - 1)) {
355                offset = end;
356                return DONE;
357            }
358            return attrString.text.charAt(++offset);
359        }
360
361        public char previous() {
362            if (offset == begin) {
363                return DONE;
364            }
365            return attrString.text.charAt(--offset);
366        }
367
368        public char setIndex(int location) {
369            if (location < begin || location > end) {
370                throw new IllegalArgumentException();
371            }
372            offset = location;
373            if (offset == end) {
374                return DONE;
375            }
376            return attrString.text.charAt(offset);
377        }
378    }
379
380    /**
381     * Constructs an {@code AttributedString} from an {@code
382     * AttributedCharacterIterator}, which represents attributed text.
383     *
384     * @param iterator
385     *            the {@code AttributedCharacterIterator} that contains the text
386     *            for this attributed string.
387     */
388    public AttributedString(AttributedCharacterIterator iterator) {
389        if (iterator.getBeginIndex() > iterator.getEndIndex()) {
390            throw new IllegalArgumentException("Invalid substring range");
391        }
392        StringBuilder buffer = new StringBuilder();
393        for (int i = iterator.getBeginIndex(); i < iterator.getEndIndex(); i++) {
394            buffer.append(iterator.current());
395            iterator.next();
396        }
397        text = buffer.toString();
398        Set<AttributedCharacterIterator.Attribute> attributes = iterator
399                .getAllAttributeKeys();
400        if (attributes == null) {
401            return;
402        }
403        attributeMap = new HashMap<Attribute, List<Range>>(
404                (attributes.size() * 4 / 3) + 1);
405
406        Iterator<Attribute> it = attributes.iterator();
407        while (it.hasNext()) {
408            AttributedCharacterIterator.Attribute attribute = it.next();
409            iterator.setIndex(0);
410            while (iterator.current() != CharacterIterator.DONE) {
411                int start = iterator.getRunStart(attribute);
412                int limit = iterator.getRunLimit(attribute);
413                Object value = iterator.getAttribute(attribute);
414                if (value != null) {
415                    addAttribute(attribute, value, start, limit);
416                }
417                iterator.setIndex(limit);
418            }
419        }
420    }
421
422    private AttributedString(AttributedCharacterIterator iterator, int start,
423            int end, Set<Attribute> attributes) {
424        if (start < iterator.getBeginIndex() || end > iterator.getEndIndex()
425                || start > end) {
426            throw new IllegalArgumentException();
427        }
428
429        if (attributes == null) {
430            return;
431        }
432
433        StringBuilder buffer = new StringBuilder();
434        iterator.setIndex(start);
435        while (iterator.getIndex() < end) {
436            buffer.append(iterator.current());
437            iterator.next();
438        }
439        text = buffer.toString();
440        attributeMap = new HashMap<Attribute, List<Range>>(
441                (attributes.size() * 4 / 3) + 1);
442
443        Iterator<Attribute> it = attributes.iterator();
444        while (it.hasNext()) {
445            AttributedCharacterIterator.Attribute attribute = it.next();
446            iterator.setIndex(start);
447            while (iterator.getIndex() < end) {
448                Object value = iterator.getAttribute(attribute);
449                int runStart = iterator.getRunStart(attribute);
450                int limit = iterator.getRunLimit(attribute);
451                if ((value instanceof Annotation && runStart >= start && limit <= end)
452                        || (value != null && !(value instanceof Annotation))) {
453                    addAttribute(attribute, value, (runStart < start ? start
454                            : runStart)
455                            - start, (limit > end ? end : limit) - start);
456                }
457                iterator.setIndex(limit);
458            }
459        }
460    }
461
462    /**
463     * Constructs an {@code AttributedString} from a range of the text contained
464     * in the specified {@code AttributedCharacterIterator}, starting at {@code
465     * start} and ending at {@code end}. All attributes will be copied to this
466     * attributed string.
467     *
468     * @param iterator
469     *            the {@code AttributedCharacterIterator} that contains the text
470     *            for this attributed string.
471     * @param start
472     *            the start index of the range of the copied text.
473     * @param end
474     *            the end index of the range of the copied text.
475     * @throws IllegalArgumentException
476     *             if {@code start} is less than first index of
477     *             {@code iterator}, {@code end} is greater than the last
478     *             index + 1 in {@code iterator} or if {@code start > end}.
479     */
480    public AttributedString(AttributedCharacterIterator iterator, int start,
481            int end) {
482        this(iterator, start, end, iterator.getAllAttributeKeys());
483    }
484
485    /**
486     * Constructs an {@code AttributedString} from a range of the text contained
487     * in the specified {@code AttributedCharacterIterator}, starting at {@code
488     * start}, ending at {@code end} and it will copy the attributes defined in
489     * the specified set. If the set is {@code null} then all attributes are
490     * copied.
491     *
492     * @param iterator
493     *            the {@code AttributedCharacterIterator} that contains the text
494     *            for this attributed string.
495     * @param start
496     *            the start index of the range of the copied text.
497     * @param end
498     *            the end index of the range of the copied text.
499     * @param attributes
500     *            the set of attributes that will be copied, or all if it is
501     *            {@code null}.
502     * @throws IllegalArgumentException
503     *             if {@code start} is less than first index of
504     *             {@code iterator}, {@code end} is greater than the last index +
505     *             1 in {@code iterator} or if {@code start > end}.
506     */
507    public AttributedString(AttributedCharacterIterator iterator, int start,
508            int end, AttributedCharacterIterator.Attribute[] attributes) {
509        this(iterator, start, end, (attributes == null
510                ? new HashSet<Attribute>()
511                : new HashSet<Attribute>(Arrays.asList(attributes))));
512    }
513
514    /**
515     * Creates an {@code AttributedString} from the given text.
516     *
517     * @param value
518     *            the text to take as base for this attributed string.
519     */
520    public AttributedString(String value) {
521        if (value == null) {
522            throw new NullPointerException("value == null");
523        }
524        text = value;
525        attributeMap = new HashMap<Attribute, List<Range>>(11);
526    }
527
528    /**
529     * Creates an {@code AttributedString} from the given text and the
530     * attributes. The whole text has the given attributes applied.
531     *
532     * @param value
533     *            the text to take as base for this attributed string.
534     * @param attributes
535     *            the attributes that the text is associated with.
536     * @throws IllegalArgumentException
537     *             if the length of {@code value} is 0 but the size of {@code
538     *             attributes} is greater than 0.
539     * @throws NullPointerException
540     *             if {@code value} is {@code null}.
541     */
542    public AttributedString(String value,
543            Map<? extends AttributedCharacterIterator.Attribute, ?> attributes) {
544        if (value == null) {
545            throw new NullPointerException("value == null");
546        }
547        if (value.length() == 0 && !attributes.isEmpty()) {
548            throw new IllegalArgumentException("Cannot add attributes to empty string");
549        }
550        text = value;
551        attributeMap = new HashMap<Attribute, List<Range>>(
552                (attributes.size() * 4 / 3) + 1);
553        Iterator<?> it = attributes.entrySet().iterator();
554        while (it.hasNext()) {
555            Map.Entry<?, ?> entry = (Map.Entry<?, ?>) it.next();
556            ArrayList<Range> ranges = new ArrayList<Range>(1);
557            ranges.add(new Range(0, text.length(), entry.getValue()));
558            attributeMap.put((AttributedCharacterIterator.Attribute) entry
559                    .getKey(), ranges);
560        }
561    }
562
563    /**
564     * Applies a given attribute to this string.
565     *
566     * @param attribute
567     *            the attribute that will be applied to this string.
568     * @param value
569     *            the value of the attribute that will be applied to this
570     *            string.
571     * @throws IllegalArgumentException
572     *             if the length of this attributed string is 0.
573     * @throws NullPointerException
574     *             if {@code attribute} is {@code null}.
575     */
576    public void addAttribute(AttributedCharacterIterator.Attribute attribute, Object value) {
577        if (attribute == null) {
578            throw new NullPointerException("attribute == null");
579        }
580        if (text.length() == 0) {
581            throw new IllegalArgumentException();
582        }
583
584        List<Range> ranges = attributeMap.get(attribute);
585        if (ranges == null) {
586            ranges = new ArrayList<Range>(1);
587            attributeMap.put(attribute, ranges);
588        } else {
589            ranges.clear();
590        }
591        ranges.add(new Range(0, text.length(), value));
592    }
593
594    /**
595     * Applies a given attribute to the given range of this string.
596     *
597     * @param attribute
598     *            the attribute that will be applied to this string.
599     * @param value
600     *            the value of the attribute that will be applied to this
601     *            string.
602     * @param start
603     *            the start of the range where the attribute will be applied.
604     * @param end
605     *            the end of the range where the attribute will be applied.
606     * @throws IllegalArgumentException
607     *             if {@code start < 0}, {@code end} is greater than the length
608     *             of this string, or if {@code start >= end}.
609     * @throws NullPointerException
610     *             if {@code attribute} is {@code null}.
611     */
612    public void addAttribute(AttributedCharacterIterator.Attribute attribute,
613            Object value, int start, int end) {
614        if (attribute == null) {
615            throw new NullPointerException("attribute == null");
616        }
617        if (start < 0 || end > text.length() || start >= end) {
618            throw new IllegalArgumentException();
619        }
620
621        if (value == null) {
622            return;
623        }
624
625        List<Range> ranges = attributeMap.get(attribute);
626        if (ranges == null) {
627            ranges = new ArrayList<Range>(1);
628            ranges.add(new Range(start, end, value));
629            attributeMap.put(attribute, ranges);
630            return;
631        }
632        ListIterator<Range> it = ranges.listIterator();
633        while (it.hasNext()) {
634            Range range = it.next();
635            if (end <= range.start) {
636                it.previous();
637                break;
638            } else if (start < range.end
639                    || (start == range.end && value.equals(range.value))) {
640                Range r1 = null, r3;
641                it.remove();
642                r1 = new Range(range.start, start, range.value);
643                r3 = new Range(end, range.end, range.value);
644
645                while (end > range.end && it.hasNext()) {
646                    range = it.next();
647                    if (end <= range.end) {
648                        if (end > range.start
649                                || (end == range.start && value.equals(range.value))) {
650                            it.remove();
651                            r3 = new Range(end, range.end, range.value);
652                            break;
653                        }
654                    } else {
655                        it.remove();
656                    }
657                }
658
659                if (value.equals(r1.value)) {
660                    if (value.equals(r3.value)) {
661                        it.add(new Range(r1.start < start ? r1.start : start,
662                                r3.end > end ? r3.end : end, r1.value));
663                    } else {
664                        it.add(new Range(r1.start < start ? r1.start : start,
665                                end, r1.value));
666                        if (r3.start < r3.end) {
667                            it.add(r3);
668                        }
669                    }
670                } else {
671                    if (value.equals(r3.value)) {
672                        if (r1.start < r1.end) {
673                            it.add(r1);
674                        }
675                        it.add(new Range(start, r3.end > end ? r3.end : end,
676                                r3.value));
677                    } else {
678                        if (r1.start < r1.end) {
679                            it.add(r1);
680                        }
681                        it.add(new Range(start, end, value));
682                        if (r3.start < r3.end) {
683                            it.add(r3);
684                        }
685                    }
686                }
687                return;
688            }
689        }
690        it.add(new Range(start, end, value));
691    }
692
693    /**
694     * Applies a given set of attributes to the given range of the string.
695     *
696     * @param attributes
697     *            the set of attributes that will be applied to this string.
698     * @param start
699     *            the start of the range where the attribute will be applied.
700     * @param end
701     *            the end of the range where the attribute will be applied.
702     * @throws IllegalArgumentException
703     *             if {@code start < 0}, {@code end} is greater than the length
704     *             of this string, or if {@code start >= end}.
705     */
706    public void addAttributes(
707            Map<? extends AttributedCharacterIterator.Attribute, ?> attributes,
708            int start, int end) {
709        Iterator<?> it = attributes.entrySet().iterator();
710        while (it.hasNext()) {
711            Map.Entry<?, ?> entry = (Map.Entry<?, ?>) it.next();
712            addAttribute(
713                    (AttributedCharacterIterator.Attribute) entry.getKey(),
714                    entry.getValue(), start, end);
715        }
716    }
717
718    /**
719     * Returns an {@code AttributedCharacterIterator} that gives access to the
720     * complete content of this attributed string.
721     *
722     * @return the newly created {@code AttributedCharacterIterator}.
723     */
724    public AttributedCharacterIterator getIterator() {
725        return new AttributedIterator(this);
726    }
727
728    /**
729     * Returns an {@code AttributedCharacterIterator} that gives access to the
730     * complete content of this attributed string. Only attributes contained in
731     * {@code attributes} are available from this iterator if they are defined
732     * for this text.
733     *
734     * @param attributes
735     *            the array containing attributes that will be in the new
736     *            iterator if they are defined for this text.
737     * @return the newly created {@code AttributedCharacterIterator}.
738     */
739    public AttributedCharacterIterator getIterator(
740            AttributedCharacterIterator.Attribute[] attributes) {
741        return new AttributedIterator(this, attributes, 0, text.length());
742    }
743
744    /**
745     * Returns an {@code AttributedCharacterIterator} that gives access to the
746     * contents of this attributed string starting at index {@code start} up to
747     * index {@code end}. Only attributes contained in {@code attributes} are
748     * available from this iterator if they are defined for this text.
749     *
750     * @param attributes
751     *            the array containing attributes that will be in the new
752     *            iterator if they are defined for this text.
753     * @param start
754     *            the start index of the iterator on the underlying text.
755     * @param end
756     *            the end index of the iterator on the underlying text.
757     * @return the newly created {@code AttributedCharacterIterator}.
758     */
759    public AttributedCharacterIterator getIterator(
760            AttributedCharacterIterator.Attribute[] attributes, int start,
761            int end) {
762        return new AttributedIterator(this, attributes, start, end);
763    }
764}
765