001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.maven.doxia.sink;
020
021import javax.swing.text.AttributeSet;
022
023import java.util.Collections;
024import java.util.Enumeration;
025import java.util.LinkedHashMap;
026import java.util.Map;
027import java.util.Map.Entry;
028import java.util.Set;
029
030/**
031 * Implementation of MutableAttributeSet using a LinkedHashMap.
032 * <p>
033 * Moved here from {@code doxia-core} in 2.2.0, so that attributes can be created without depending on
034 * Doxia internals. The deprecated {@code org.apache.maven.doxia.sink.impl.SinkEventAttributeSet} remains
035 * as a subclass of this class for binary compatibility, which means the constants declared here, and every
036 * attribute set Doxia itself creates, are instances of this class and no longer of that one.
037 *
038 * @author ltheussl
039 * @since 1.1
040 */
041public class SinkEventAttributeSet implements SinkEventAttributes, Cloneable {
042    /**
043     * An unmodifiable attribute set containing only an underline attribute.
044     */
045    public static final SinkEventAttributes UNDERLINE;
046
047    /**
048     * An unmodifiable attribute set containing only an overline attribute.
049     */
050    public static final SinkEventAttributes OVERLINE;
051
052    /**
053     * An unmodifiable attribute set containing only a linethrough attribute.
054     */
055    public static final SinkEventAttributes LINETHROUGH;
056
057    /**
058     * An unmodifiable attribute set containing only a source attribute.
059     */
060    public static final SinkEventAttributes SOURCE;
061
062    /**
063     * An unmodifiable attribute set containing only a bold attribute.
064     */
065    public static final SinkEventAttributes BOLD;
066
067    /**
068     * An unmodifiable attribute set containing only an italic attribute.
069     */
070    public static final SinkEventAttributes ITALIC;
071
072    /**
073     * An unmodifiable attribute set containing only a monospaced attribute.
074     */
075    public static final SinkEventAttributes MONOSPACED;
076
077    /**
078     * An unmodifiable attribute set containing only a left attribute.
079     */
080    public static final SinkEventAttributes LEFT;
081
082    /**
083     * An unmodifiable attribute set containing only a right attribute.
084     */
085    public static final SinkEventAttributes RIGHT;
086
087    /**
088     * An unmodifiable attribute set containing only a center attribute.
089     */
090    public static final SinkEventAttributes CENTER;
091
092    /**
093     * An unmodifiable attribute set containing only a justify attribute.
094     */
095    public static final SinkEventAttributes JUSTIFY;
096
097    static {
098        UNDERLINE = new SinkEventAttributeSet(DECORATION, "underline").unmodifiable();
099        OVERLINE = new SinkEventAttributeSet(DECORATION, "overline").unmodifiable();
100        LINETHROUGH = new SinkEventAttributeSet(DECORATION, "line-through").unmodifiable();
101        SOURCE = new SinkEventAttributeSet(DECORATION, "source").unmodifiable();
102
103        BOLD = new SinkEventAttributeSet(STYLE, "bold").unmodifiable();
104        ITALIC = new SinkEventAttributeSet(STYLE, "italic").unmodifiable();
105        MONOSPACED = new SinkEventAttributeSet(STYLE, "monospaced").unmodifiable();
106
107        LEFT = new SinkEventAttributeSet(ALIGN, "left").unmodifiable();
108        RIGHT = new SinkEventAttributeSet(ALIGN, "right").unmodifiable();
109        CENTER = new SinkEventAttributeSet(ALIGN, "center").unmodifiable();
110        JUSTIFY = new SinkEventAttributeSet(ALIGN, "justify").unmodifiable();
111    }
112
113    private Map<String, Object> attribs;
114
115    private AttributeSet resolveParent;
116
117    /**
118     * Constructs a new, empty SinkEventAttributeSet with default size 5.
119     */
120    public SinkEventAttributeSet() {
121        this(5);
122    }
123
124    /**
125     * Constructs a new, empty SinkEventAttributeSet with the specified initial size.
126     *
127     * @param size the initial number of attribs.
128     */
129    public SinkEventAttributeSet(int size) {
130        attribs = new LinkedHashMap<>(size);
131    }
132
133    /**
134     * Constructs a new SinkEventAttributeSet with the attribute name-value
135     * mappings as given by the specified String array.
136     * Each even index of the array is an attribute name, and the following odd index is the corresponding attribute value.
137     * This constructor only supports String attribute values.
138     *
139     * @param attributes the specified String array. If the length of this array
140     * is not an even number, an IllegalArgumentException is thrown.
141     */
142    public SinkEventAttributeSet(String... attributes) {
143        int n = attributes.length;
144
145        if ((n % 2) != 0) {
146            throw new IllegalArgumentException("Missing attribute!");
147        }
148
149        attribs = new LinkedHashMap<>(n / 2);
150
151        for (int i = 0; i < n; i += 2) {
152            attribs.put(attributes[i], attributes[i + 1]);
153        }
154    }
155
156    /**
157     * Constructs a new SinkEventAttributeSet with the same attribute name-value
158     * mappings as in the specified AttributeSet.
159     *
160     * @param attributes the specified AttributeSet.
161     */
162    public SinkEventAttributeSet(AttributeSet attributes) {
163        attribs = new LinkedHashMap<>(attributes.getAttributeCount());
164
165        Enumeration<?> names = attributes.getAttributeNames();
166
167        while (names.hasMoreElements()) {
168            Object name = names.nextElement();
169
170            attribs.put(name.toString(), attributes.getAttribute(name));
171        }
172    }
173
174    /**
175     * Replace this AttributeSet by an unmodifiable view of itself.
176     * Any subsequent attempt to add, remove or modify the underlying mapping
177     * will result in an UnsupportedOperationException.
178     *
179     * @return an unmodifiable view of this AttributeSet.
180     * @since 1.1.1
181     */
182    public SinkEventAttributeSet unmodifiable() {
183        this.attribs = Collections.unmodifiableMap(attribs);
184
185        return this;
186    }
187
188    /**
189     * Checks whether the set of attribs is empty.
190     *
191     * @return true if the set is empty.
192     */
193    public boolean isEmpty() {
194        return attribs.isEmpty();
195    }
196
197    /**
198     * {@inheritDoc}
199     *
200     * @return a int.
201     */
202    public int getAttributeCount() {
203        return attribs.size();
204    }
205
206    public boolean isDefined(Object attrName) {
207        return attribs.containsKey(attrName);
208    }
209
210    public boolean isEqual(AttributeSet attr) {
211        return ((getAttributeCount() == attr.getAttributeCount()) && containsAttributes(attr));
212    }
213
214    /**
215     * {@inheritDoc}
216     *
217     * @return a {@link javax.swing.text.AttributeSet} object.
218     */
219    public AttributeSet copyAttributes() {
220        return ((AttributeSet) clone());
221    }
222
223    /**
224     * {@inheritDoc}
225     *
226     * @return a {@link java.util.Enumeration} object.
227     */
228    public Enumeration<String> getAttributeNames() {
229        return Collections.enumeration(attribs.keySet());
230    }
231
232    public Object getAttribute(Object key) {
233        Object value = attribs.get(key);
234
235        if (value == null) {
236            AttributeSet parent = getResolveParent();
237
238            if (parent != null) {
239                value = parent.getAttribute(key);
240            }
241        }
242
243        return value;
244    }
245
246    public boolean containsAttribute(Object name, Object value) {
247        return value.equals(getAttribute(name));
248    }
249
250    public boolean containsAttributes(AttributeSet attributes) {
251        boolean result = true;
252
253        Enumeration<?> names = attributes.getAttributeNames();
254
255        while (result && names.hasMoreElements()) {
256            Object name = names.nextElement();
257            result = attributes.getAttribute(name).equals(getAttribute(name));
258        }
259
260        return result;
261    }
262
263    /**
264     * {@inheritDoc}
265     *
266     * Adds an attribute with the given name and value.
267     */
268    public void addAttribute(Object name, Object value) {
269        attribs.put(name.toString(), value);
270    }
271
272    public void addAttributes(AttributeSet attributes) {
273        if (attributes == null || attributes.getAttributeCount() == 0) {
274            return;
275        }
276
277        Enumeration<?> names = attributes.getAttributeNames();
278
279        while (names.hasMoreElements()) {
280            Object name = names.nextElement();
281
282            addAttribute(name, attributes.getAttribute(name));
283        }
284    }
285
286    public void removeAttribute(Object name) {
287        attribs.remove(name);
288    }
289
290    public void removeAttributes(Enumeration<?> names) {
291        while (names.hasMoreElements()) {
292            removeAttribute(names.nextElement());
293        }
294    }
295
296    /**
297     * {@inheritDoc}
298     *
299     * @param attributes a {@link javax.swing.text.AttributeSet} object.
300     */
301    public void removeAttributes(AttributeSet attributes) {
302        if (attributes == null) {
303            return;
304        } else if (attributes == this) {
305            attribs.clear();
306        } else {
307            Enumeration<?> names = attributes.getAttributeNames();
308
309            while (names.hasMoreElements()) {
310                Object name = names.nextElement();
311                Object value = attributes.getAttribute(name);
312
313                if (value.equals(getAttribute(name))) {
314                    removeAttribute(name);
315                }
316            }
317        }
318    }
319
320    /**
321     * {@inheritDoc}
322     *
323     * @return a {@link javax.swing.text.AttributeSet} object.
324     */
325    public AttributeSet getResolveParent() {
326        return this.resolveParent;
327    }
328
329    public void setResolveParent(AttributeSet parent) {
330        this.resolveParent = parent;
331    }
332
333    @Override
334    public Set<Entry<String, Object>> entrySet() {
335        return attribs.entrySet();
336    }
337
338    @Override
339    public Object clone() {
340        // deliberately not "new SinkEventAttributeSet(...)": a subclass must clone to its own type
341        SinkEventAttributeSet attr;
342        try {
343            attr = (SinkEventAttributeSet) super.clone();
344        } catch (CloneNotSupportedException e) {
345            throw new AssertionError(e);
346        }
347
348        attr.attribs = new LinkedHashMap<>(attribs);
349
350        if (resolveParent != null) {
351            attr.resolveParent = resolveParent.copyAttributes();
352        }
353
354        return attr;
355    }
356
357    @Override
358    public int hashCode() {
359        return attribs.hashCode();
360    }
361
362    @Override
363    public boolean equals(Object obj) {
364        if (this == obj) {
365            return true;
366        }
367
368        if (obj instanceof SinkEventAttributeSet) {
369            return isEqual((SinkEventAttributeSet) obj);
370        }
371
372        return false;
373    }
374
375    @Override
376    public String toString() {
377        StringBuilder s = new StringBuilder();
378
379        for (Map.Entry<String, Object> attribute : entrySet()) {
380            s.append(' ')
381                    .append(attribute.getKey())
382                    .append('=')
383                    .append(attribute.getValue().toString());
384        }
385
386        return s.toString();
387    }
388
389    /**
390     * Attribute sets for the semantic attribute.
391     */
392    public static class Semantics {
393        /**
394         * An unmodifiable attribute set containing only an emphasis attribute.
395         */
396        public static final SinkEventAttributes EMPHASIS;
397
398        /**
399         * An unmodifiable attribute set containing only a strong attribute.
400         */
401        public static final SinkEventAttributes STRONG;
402
403        /**
404         * An unmodifiable attribute set containing only a small attribute.
405         */
406        public static final SinkEventAttributes SMALL;
407
408        /**
409         * An unmodifiable attribute set containing only a line-through attribute.
410         */
411        public static final SinkEventAttributes LINE_THROUGH;
412
413        /**
414         * An unmodifiable attribute set containing only a citation attribute.
415         */
416        public static final SinkEventAttributes CITATION;
417
418        /**
419         * An unmodifiable attribute set containing only a quote attribute.
420         */
421        public static final SinkEventAttributes QUOTE;
422
423        /**
424         * An unmodifiable attribute set containing only a definition attribute.
425         */
426        public static final SinkEventAttributes DEFINITION;
427
428        /**
429         * An unmodifiable attribute set containing only an abbreviation attribute.
430         */
431        public static final SinkEventAttributes ABBREVIATION;
432
433        /**
434         * An unmodifiable attribute set containing only an italic attribute.
435         */
436        public static final SinkEventAttributes ITALIC;
437
438        /**
439         * An unmodifiable attribute set containing only a bold attribute.
440         */
441        public static final SinkEventAttributes BOLD;
442
443        /**
444         * An unmodifiable attribute set containing only a monospaced attribute.
445         */
446        public static final SinkEventAttributes MONOSPACED;
447
448        /**
449         * An unmodifiable attribute set containing only a code attribute.
450         */
451        public static final SinkEventAttributes CODE;
452
453        /**
454         * An unmodifiable attribute set containing only a variable attribute.
455         */
456        public static final SinkEventAttributes VARIABLE;
457
458        /**
459         * An unmodifiable attribute set containing only a sample attribute.
460         */
461        public static final SinkEventAttributes SAMPLE;
462
463        /**
464         * An unmodifiable attribute set containing only a keyboard attribute.
465         */
466        public static final SinkEventAttributes KEYBOARD;
467
468        /**
469         * An unmodifiable attribute set containing only a superscript attribute.
470         */
471        public static final SinkEventAttributes SUPERSCRIPT;
472
473        /**
474         * An unmodifiable attribute set containing only a subscript attribute.
475         */
476        public static final SinkEventAttributes SUBSCRIPT;
477
478        /**
479         * An unmodifiable attribute set containing only an annotation attribute.
480         */
481        public static final SinkEventAttributes ANNOTATION;
482
483        /**
484         * An unmodifiable attribute set containing only a highlight attribute.
485         */
486        public static final SinkEventAttributes HIGHLIGHT;
487
488        /**
489         * An unmodifiable attribute set containing only a ruby attribute.
490         */
491        public static final SinkEventAttributes RUBY;
492
493        /**
494         * An unmodifiable attribute set containing only a rubyBase attribute.
495         */
496        public static final SinkEventAttributes RUBY_BASE;
497
498        /**
499         * An unmodifiable attribute set containing only a rubyText attribute.
500         */
501        public static final SinkEventAttributes RUBY_TEXT;
502
503        /**
504         * An unmodifiable attribute set containing only a rubyTextContainer attribute.
505         */
506        public static final SinkEventAttributes RUBY_TEXT_CONTAINER;
507
508        /**
509         * An unmodifiable attribute set containing only a rubyParentheses attribute.
510         */
511        public static final SinkEventAttributes RUBY_PARANTHESES;
512
513        /**
514         * An unmodifiable attribute set containing only a bidirectionalIsolation attribute.
515         */
516        public static final SinkEventAttributes BIDIRECTIONAL_ISOLATION;
517
518        /**
519         * An unmodifiable attribute set containing only a bidirectionalOverride attribute.
520         */
521        public static final SinkEventAttributes BIDIRECTIONAL_OVERRIDE;
522
523        /**
524         * An unmodifiable attribute set containing only a phrase attribute.
525         */
526        public static final SinkEventAttributes PHRASE;
527
528        /**
529         * An unmodifiable attribute set containing only an insert attribute.
530         */
531        public static final SinkEventAttributes INSERT;
532
533        /**
534         * An unmodifiable attribute set containing only a delete attribute.
535         */
536        public static final SinkEventAttributes DELETE;
537
538        static {
539            EMPHASIS = new SinkEventAttributeSet(SEMANTICS, "emphasis").unmodifiable();
540            STRONG = new SinkEventAttributeSet(SEMANTICS, "strong").unmodifiable();
541            SMALL = new SinkEventAttributeSet(SEMANTICS, "small").unmodifiable();
542            LINE_THROUGH = new SinkEventAttributeSet(SEMANTICS, "line-through").unmodifiable();
543            CITATION = new SinkEventAttributeSet(SEMANTICS, "citation").unmodifiable();
544            QUOTE = new SinkEventAttributeSet(SEMANTICS, "quote").unmodifiable();
545            DEFINITION = new SinkEventAttributeSet(SEMANTICS, "definition").unmodifiable();
546            ABBREVIATION = new SinkEventAttributeSet(SEMANTICS, "abbreviation").unmodifiable();
547            ITALIC = new SinkEventAttributeSet(SEMANTICS, "italic").unmodifiable();
548            BOLD = new SinkEventAttributeSet(SEMANTICS, "bold").unmodifiable();
549            MONOSPACED = new SinkEventAttributeSet(SEMANTICS, "monospaced").unmodifiable();
550            CODE = new SinkEventAttributeSet(SEMANTICS, "code").unmodifiable();
551            VARIABLE = new SinkEventAttributeSet(SEMANTICS, "variable").unmodifiable();
552            SAMPLE = new SinkEventAttributeSet(SEMANTICS, "sample").unmodifiable();
553            KEYBOARD = new SinkEventAttributeSet(SEMANTICS, "keyboard").unmodifiable();
554            SUPERSCRIPT = new SinkEventAttributeSet(SEMANTICS, "superscript").unmodifiable();
555            SUBSCRIPT = new SinkEventAttributeSet(SEMANTICS, "subscript").unmodifiable();
556            ANNOTATION = new SinkEventAttributeSet(SEMANTICS, "annotation").unmodifiable();
557            HIGHLIGHT = new SinkEventAttributeSet(SEMANTICS, "highlight").unmodifiable();
558            RUBY = new SinkEventAttributeSet(SEMANTICS, "ruby").unmodifiable();
559            RUBY_BASE = new SinkEventAttributeSet(SEMANTICS, "rubyBase").unmodifiable();
560            RUBY_TEXT = new SinkEventAttributeSet(SEMANTICS, "rubyText").unmodifiable();
561            RUBY_TEXT_CONTAINER = new SinkEventAttributeSet(SEMANTICS, "rubyTextContainer").unmodifiable();
562            RUBY_PARANTHESES = new SinkEventAttributeSet(SEMANTICS, "rubyParentheses").unmodifiable();
563            BIDIRECTIONAL_ISOLATION = new SinkEventAttributeSet(SEMANTICS, "bidirectionalIsolation").unmodifiable();
564            BIDIRECTIONAL_OVERRIDE = new SinkEventAttributeSet(SEMANTICS, "bidirectionalOverride").unmodifiable();
565            PHRASE = new SinkEventAttributeSet(SEMANTICS, "phrase").unmodifiable();
566            INSERT = new SinkEventAttributeSet(SEMANTICS, "insert").unmodifiable();
567            DELETE = new SinkEventAttributeSet(SEMANTICS, "delete").unmodifiable();
568        }
569    }
570}