001package org.apache.maven.doxia.sink;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.util.Collections;
023import java.util.Enumeration;
024import java.util.LinkedHashMap;
025import java.util.Map;
026
027import javax.swing.text.AttributeSet;
028
029/**
030 * Implementation of MutableAttributeSet using a LinkedHashMap.
031 *
032 * @author ltheussl
033 * @version $Id$
034 * @since 1.1
035 */
036public class SinkEventAttributeSet
037    implements SinkEventAttributes, Cloneable
038{
039    /**
040     * An unmodifiable attribute set containing only an underline attribute.
041     */
042    public static final SinkEventAttributes UNDERLINE;
043
044    /**
045     * An unmodifiable attribute set containing only an overline attribute.
046     */
047    public static final SinkEventAttributes OVERLINE;
048
049    /**
050     * An unmodifiable attribute set containing only a linethrough attribute.
051     */
052    public static final SinkEventAttributes LINETHROUGH;
053
054    /**
055     * An unmodifiable attribute set containing only a boxed attribute.
056     */
057    public static final SinkEventAttributes BOXED;
058
059    /**
060     * An unmodifiable attribute set containing only a bold attribute.
061     */
062    public static final SinkEventAttributes BOLD;
063
064    /**
065     * An unmodifiable attribute set containing only an italic attribute.
066     */
067    public static final SinkEventAttributes ITALIC;
068
069    /**
070     * An unmodifiable attribute set containing only a monospaced attribute.
071     */
072    public static final SinkEventAttributes MONOSPACED;
073
074    /**
075     * An unmodifiable attribute set containing only a left attribute.
076     */
077    public static final SinkEventAttributes LEFT;
078
079    /**
080     * An unmodifiable attribute set containing only a right attribute.
081     */
082    public static final SinkEventAttributes RIGHT;
083
084    /**
085     * An unmodifiable attribute set containing only a center attribute.
086     */
087    public static final SinkEventAttributes CENTER;
088
089    /**
090     * An unmodifiable attribute set containing only a justify attribute.
091     */
092    public static final SinkEventAttributes JUSTIFY;
093
094
095    static
096    {
097        UNDERLINE = new SinkEventAttributeSet( new String[] {DECORATION, "underline"} ).unmodifiable();
098        OVERLINE = new SinkEventAttributeSet( new String[] {DECORATION, "overline"} ).unmodifiable();
099        LINETHROUGH = new SinkEventAttributeSet( new String[] {DECORATION, "line-through"} ).unmodifiable();
100        BOXED = new SinkEventAttributeSet( new String[] {DECORATION, "boxed"} ).unmodifiable();
101
102        BOLD = new SinkEventAttributeSet( new String[] {STYLE, "bold"} ).unmodifiable();
103        ITALIC = new SinkEventAttributeSet( new String[] {STYLE, "italic"} ).unmodifiable();
104        MONOSPACED = new SinkEventAttributeSet( new String[] {STYLE, "monospaced"} ).unmodifiable();
105
106        LEFT = new SinkEventAttributeSet( new String[] {ALIGN, "left"} ).unmodifiable();
107        RIGHT = new SinkEventAttributeSet( new String[] {ALIGN, "right"} ).unmodifiable();
108        CENTER = new SinkEventAttributeSet( new String[] {ALIGN, "center"} ).unmodifiable();
109        JUSTIFY = new SinkEventAttributeSet( new String[] {ALIGN, "justify"} ).unmodifiable();
110    }
111
112    private Map<String, Object> attribs;
113
114    private AttributeSet resolveParent;
115
116    /**
117     * Constructs a new, empty SinkEventAttributeSet with default size 5.
118     */
119    public SinkEventAttributeSet()
120    {
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    {
131        attribs = new LinkedHashMap<String, Object>( size );
132    }
133
134    /**
135     * Constructs a new SinkEventAttributeSet with the attribute name-value
136     * mappings as given by the specified String array.
137     *
138     * @param attributes the specified String array. If the length of this array
139     * is not an even number, an IllegalArgumentException is thrown.
140     */
141    public SinkEventAttributeSet( String... attributes )
142    {
143        int n = attributes.length;
144
145        if ( ( n % 2 ) != 0 )
146        {
147            throw new IllegalArgumentException( "Missing attribute!" );
148        }
149
150        attribs = new LinkedHashMap<String, Object>( n / 2 );
151
152        for ( int i = 0; i < n; i += 2 )
153        {
154            attribs.put( attributes[i], attributes[i + 1] );
155        }
156    }
157
158    /**
159     * Constructs a new SinkEventAttributeSet with the same attribute name-value
160     * mappings as in the specified AttributeSet.
161     *
162     * @param attributes the specified AttributeSet.
163     */
164    public SinkEventAttributeSet( AttributeSet attributes )
165    {
166        attribs = new LinkedHashMap<String, Object>( attributes.getAttributeCount() );
167
168        Enumeration<?> names = attributes.getAttributeNames();
169
170        while ( names.hasMoreElements() )
171        {
172            Object name = names.nextElement();
173
174            attribs.put( name.toString(), attributes.getAttribute( name ) );
175        }
176    }
177
178    /**
179     * Replace this AttributeSet by an unmodifiable view of itself.
180     * Any subsequent attempt to add, remove or modify the underlying mapping
181     * will result in an UnsupportedOperationException.
182     *
183     * @return an unmodifiable view of this AttributeSet.
184     *
185     * @since 1.1.1
186     */
187    public SinkEventAttributeSet unmodifiable()
188    {
189        this.attribs = Collections.unmodifiableMap( attribs );
190
191        return this;
192    }
193
194    /**
195     * Checks whether the set of attribs is empty.
196     *
197     * @return true if the set is empty.
198     */
199    public boolean isEmpty()
200    {
201        return attribs.isEmpty();
202    }
203
204    /** {@inheritDoc} */
205    public int getAttributeCount()
206    {
207        return attribs.size();
208    }
209
210    /** {@inheritDoc} */
211    public boolean isDefined( Object attrName )
212    {
213        return attribs.containsKey( attrName );
214    }
215
216    /** {@inheritDoc} */
217    public boolean isEqual( AttributeSet attr )
218    {
219        return ( ( getAttributeCount() == attr.getAttributeCount() )
220                && containsAttributes( attr ) );
221    }
222
223    /** {@inheritDoc} */
224    public AttributeSet copyAttributes()
225    {
226        return ( (AttributeSet) clone() );
227    }
228
229    /** {@inheritDoc} */
230    public Enumeration<String> getAttributeNames()
231    {
232        return Collections.enumeration( attribs.keySet() );
233    }
234
235    /** {@inheritDoc} */
236    public Object getAttribute( Object key  )
237    {
238        Object value = attribs.get( key  );
239
240        if ( value == null )
241        {
242            AttributeSet parent = getResolveParent();
243
244            if ( parent != null )
245            {
246                value = parent.getAttribute( key  );
247            }
248        }
249
250        return value;
251    }
252
253    /** {@inheritDoc} */
254    public boolean containsAttribute( Object name, Object value )
255    {
256        return value.equals( getAttribute( name ) );
257    }
258
259    /** {@inheritDoc} */
260    public boolean containsAttributes( AttributeSet attributes )
261    {
262        boolean result = true;
263
264        Enumeration<?> names = attributes.getAttributeNames();
265
266        while ( result && names.hasMoreElements() )
267        {
268            Object name = names.nextElement();
269            result = attributes.getAttribute( name ).equals( getAttribute( name ) );
270        }
271
272        return result;
273    }
274
275    /**
276     * {@inheritDoc}
277     *
278     * Adds an attribute with the given name and value.
279     */
280    public void addAttribute( Object name, Object value )
281    {
282        attribs.put( name.toString(), value );
283    }
284
285    /** {@inheritDoc} */
286    public void addAttributes( AttributeSet attributes  )
287    {
288        if ( attributes == null || attributes.getAttributeCount() == 0 )
289        {
290            return;
291        }
292
293        Enumeration<?> names = attributes.getAttributeNames();
294
295        while ( names.hasMoreElements() )
296        {
297            Object name = names.nextElement();
298
299            addAttribute( name, attributes.getAttribute( name ) );
300        }
301    }
302
303    /** {@inheritDoc} */
304    public void removeAttribute( Object name )
305    {
306        attribs.remove( name );
307    }
308
309    /** {@inheritDoc} */
310    public void removeAttributes( Enumeration<?> names )
311    {
312        while ( names.hasMoreElements() )
313        {
314            removeAttribute( names.nextElement() );
315        }
316    }
317
318    /** {@inheritDoc} */
319    public void removeAttributes( AttributeSet attributes  )
320    {
321        if ( attributes == null )
322        {
323            return;
324        }
325        else if ( attributes == this )
326        {
327            attribs.clear();
328        }
329        else
330        {
331            Enumeration<?> names = attributes.getAttributeNames();
332
333            while ( names.hasMoreElements() )
334            {
335                Object name = names.nextElement();
336                Object value = attributes.getAttribute( name );
337
338                if ( value.equals( getAttribute( name ) ) )
339                {
340                    removeAttribute( name );
341                }
342            }
343        }
344    }
345
346    /** {@inheritDoc} */
347    public AttributeSet getResolveParent()
348    {
349        return this.resolveParent;
350    }
351
352    /** {@inheritDoc} */
353    public void setResolveParent( AttributeSet parent )
354    {
355        this.resolveParent = parent;
356    }
357
358    /** {@inheritDoc} */
359    @Override
360    public Object clone()
361    {
362        SinkEventAttributeSet attr = new SinkEventAttributeSet( attribs.size() );
363        attr.attribs = new LinkedHashMap<String, Object>( attribs );
364
365        if ( resolveParent != null )
366        {
367            attr.resolveParent = resolveParent.copyAttributes();
368        }
369
370        return attr;
371    }
372
373    /** {@inheritDoc} */
374    @Override
375    public int hashCode()
376    {
377        final int parentHash = ( resolveParent == null ? 0 : resolveParent.hashCode() );
378
379        return attribs.hashCode() + parentHash;
380    }
381
382    /** {@inheritDoc} */
383    @Override
384    public boolean equals( Object obj )
385    {
386        if ( this == obj )
387        {
388            return true;
389        }
390
391        if ( obj instanceof SinkEventAttributeSet )
392        {
393            return isEqual( (SinkEventAttributeSet) obj  );
394        }
395
396        return false;
397    }
398
399    /** {@inheritDoc} */
400    @Override
401    public String toString()
402    {
403        StringBuilder s = new StringBuilder();
404        Enumeration<String> names = getAttributeNames();
405
406        while ( names.hasMoreElements() )
407        {
408            String key = names.nextElement();
409            String value = getAttribute( key ).toString();
410
411            s.append( ' ' ).append( key ).append( '=' ).append( value );
412        }
413
414        return s.toString();
415    }
416
417}