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