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}