1 /*
   2  * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing.text;
  26 
  27 import java.util.Hashtable;
  28 import java.util.Enumeration;
  29 import java.util.Collections;
  30 import java.io.IOException;
  31 import java.io.ObjectInputStream;
  32 import java.io.Serializable;
  33 import java.util.AbstractMap;
  34 import java.util.LinkedHashMap;
  35 
  36 /**
  37  * A straightforward implementation of MutableAttributeSet using a
  38  * hash table.
  39  * <p>
  40  * <strong>Warning:</strong>
  41  * Serialized objects of this class will not be compatible with
  42  * future Swing releases. The current serialization support is
  43  * appropriate for short term storage or RMI between applications running
  44  * the same version of Swing.  As of 1.4, support for long term storage
  45  * of all JavaBeans&trade;
  46  * has been added to the <code>java.beans</code> package.
  47  * Please see {@link java.beans.XMLEncoder}.
  48  *
  49  * @author Tim Prinzing
  50  */
  51 @SuppressWarnings("serial") // Same-version serialization only
  52 public class SimpleAttributeSet implements MutableAttributeSet, Serializable, Cloneable
  53 {
  54     private static final long serialVersionUID = -6631553454711782652L;
  55 
  56     /**
  57      * An empty attribute set.
  58      */
  59     public static final AttributeSet EMPTY = new EmptyAttributeSet();
  60 
  61     private transient LinkedHashMap<Object, Object> table = new LinkedHashMap<>(3);
  62 
  63     /**
  64      * Creates a new attribute set.
  65      */
  66     public SimpleAttributeSet() {
  67     }
  68 
  69     /**
  70      * Creates a new attribute set based on a supplied set of attributes.
  71      *
  72      * @param source the set of attributes
  73      */
  74     public SimpleAttributeSet(AttributeSet source) {
  75         addAttributes(source);
  76     }
  77 
  78     /**
  79      * Checks whether the set of attributes is empty.
  80      *
  81      * @return true if the set is empty else false
  82      */
  83     public boolean isEmpty()
  84     {
  85         return table.isEmpty();
  86     }
  87 
  88     /**
  89      * Gets a count of the number of attributes.
  90      *
  91      * @return the count
  92      */
  93     public int getAttributeCount() {
  94         return table.size();
  95     }
  96 
  97     /**
  98      * Tells whether a given attribute is defined.
  99      *
 100      * @param attrName the attribute name
 101      * @return true if the attribute is defined
 102      */
 103     public boolean isDefined(Object attrName) {
 104         return table.containsKey(attrName);
 105     }
 106 
 107     /**
 108      * Compares two attribute sets.
 109      *
 110      * @param attr the second attribute set
 111      * @return true if the sets are equal, false otherwise
 112      */
 113     public boolean isEqual(AttributeSet attr) {
 114         return ((getAttributeCount() == attr.getAttributeCount()) &&
 115                 containsAttributes(attr));
 116     }
 117 
 118     /**
 119      * Makes a copy of the attributes.
 120      *
 121      * @return the copy
 122      */
 123     public AttributeSet copyAttributes() {
 124         return (AttributeSet) clone();
 125     }
 126 
 127     /**
 128      * Gets the names of the attributes in the set.
 129      *
 130      * @return the names as an <code>Enumeration</code>
 131      */
 132     public Enumeration<?> getAttributeNames() {
 133         return Collections.enumeration(table.keySet());
 134     }
 135 
 136     /**
 137      * Gets the value of an attribute.
 138      *
 139      * @param name the attribute name
 140      * @return the value
 141      */
 142     public Object getAttribute(Object name) {
 143         Object value = table.get(name);
 144         if (value == null) {
 145             AttributeSet parent = getResolveParent();
 146             if (parent != null) {
 147                 value = parent.getAttribute(name);
 148             }
 149         }
 150         return value;
 151     }
 152 
 153     /**
 154      * Checks whether the attribute list contains a
 155      * specified attribute name/value pair.
 156      *
 157      * @param name the name
 158      * @param value the value
 159      * @return true if the name/value pair is in the list
 160      */
 161     public boolean containsAttribute(Object name, Object value) {
 162         return value.equals(getAttribute(name));
 163     }
 164 
 165     /**
 166      * Checks whether the attribute list contains all the
 167      * specified name/value pairs.
 168      *
 169      * @param attributes the attribute list
 170      * @return true if the list contains all the name/value pairs
 171      */
 172     public boolean containsAttributes(AttributeSet attributes) {
 173         boolean result = true;
 174 
 175         Enumeration<?> names = attributes.getAttributeNames();
 176         while (result && names.hasMoreElements()) {
 177             Object name = names.nextElement();
 178             result = attributes.getAttribute(name).equals(getAttribute(name));
 179         }
 180 
 181         return result;
 182     }
 183 
 184     /**
 185      * Adds an attribute to the list.
 186      *
 187      * @param name the attribute name
 188      * @param value the attribute value
 189      */
 190     public void addAttribute(Object name, Object value) {
 191         table.put(name, value);
 192     }
 193 
 194     /**
 195      * Adds a set of attributes to the list.
 196      *
 197      * @param attributes the set of attributes to add
 198      */
 199     public void addAttributes(AttributeSet attributes) {
 200         Enumeration<?> names = attributes.getAttributeNames();
 201         while (names.hasMoreElements()) {
 202             Object name = names.nextElement();
 203             addAttribute(name, attributes.getAttribute(name));
 204         }
 205     }
 206 
 207     /**
 208      * Removes an attribute from the list.
 209      *
 210      * @param name the attribute name
 211      */
 212     public void removeAttribute(Object name) {
 213         table.remove(name);
 214     }
 215 
 216     /**
 217      * Removes a set of attributes from the list.
 218      *
 219      * @param names the set of names to remove
 220      */
 221     public void removeAttributes(Enumeration<?> names) {
 222         while (names.hasMoreElements())
 223             removeAttribute(names.nextElement());
 224     }
 225 
 226     /**
 227      * Removes a set of attributes from the list.
 228      *
 229      * @param attributes the set of attributes to remove
 230      */
 231     public void removeAttributes(AttributeSet attributes) {
 232         if (attributes == this) {
 233             table.clear();
 234         }
 235         else {
 236             Enumeration<?> names = attributes.getAttributeNames();
 237             while (names.hasMoreElements()) {
 238                 Object name = names.nextElement();
 239                 Object value = attributes.getAttribute(name);
 240                 if (value.equals(getAttribute(name)))
 241                     removeAttribute(name);
 242             }
 243         }
 244     }
 245 
 246     /**
 247      * Gets the resolving parent.  This is the set
 248      * of attributes to resolve through if an attribute
 249      * isn't defined locally.  This is null if there
 250      * are no other sets of attributes to resolve
 251      * through.
 252      *
 253      * @return the parent
 254      */
 255     public AttributeSet getResolveParent() {
 256         return (AttributeSet) table.get(StyleConstants.ResolveAttribute);
 257     }
 258 
 259     /**
 260      * Sets the resolving parent.
 261      *
 262      * @param parent the parent
 263      */
 264     public void setResolveParent(AttributeSet parent) {
 265         addAttribute(StyleConstants.ResolveAttribute, parent);
 266     }
 267 
 268     // --- Object methods ---------------------------------
 269 
 270     /**
 271      * Clones a set of attributes.
 272      *
 273      * @return the new set of attributes
 274      */
 275     @SuppressWarnings("unchecked") // Cast of result of clone
 276     public Object clone() {
 277         SimpleAttributeSet attr;
 278         try {
 279             attr = (SimpleAttributeSet) super.clone();
 280             attr.table = (LinkedHashMap) table.clone();
 281         } catch (CloneNotSupportedException cnse) {
 282             attr = null;
 283         }
 284         return attr;
 285     }
 286 
 287     /**
 288      * Returns a hashcode for this set of attributes.
 289      * @return     a hashcode value for this set of attributes.
 290      */
 291     public int hashCode() {
 292         return table.hashCode();
 293     }
 294 
 295     /**
 296      * Compares this object to the specified object.
 297      * The result is <code>true</code> if the object is an equivalent
 298      * set of attributes.
 299      * @param     obj   the object to compare this attribute set with
 300      * @return    <code>true</code> if the objects are equal;
 301      *            <code>false</code> otherwise
 302      */
 303     public boolean equals(Object obj) {
 304         if (this == obj) {
 305             return true;
 306         }
 307         if (obj instanceof AttributeSet) {
 308             AttributeSet attrs = (AttributeSet) obj;
 309             return isEqual(attrs);
 310         }
 311         return false;
 312     }
 313 
 314     /**
 315      * Converts the attribute set to a String.
 316      *
 317      * @return the string
 318      */
 319     public String toString() {
 320         String s = "";
 321         Enumeration<?> names = getAttributeNames();
 322         while (names.hasMoreElements()) {
 323             Object key = names.nextElement();
 324             Object value = getAttribute(key);
 325             if (value instanceof AttributeSet) {
 326                 // don't go recursive
 327                 s = s + key + "=**AttributeSet** ";
 328             } else {
 329                 s = s + key + "=" + value + " ";
 330             }
 331         }
 332         return s;
 333     }
 334 
 335     private void writeObject(java.io.ObjectOutputStream s) throws IOException {
 336         s.defaultWriteObject();
 337         StyleContext.writeAttributeSet(s, this);
 338     }
 339 
 340     private void readObject(ObjectInputStream s)
 341       throws ClassNotFoundException, IOException {
 342         s.defaultReadObject();
 343         table = new LinkedHashMap<>(3);
 344         StyleContext.readAttributeSet(s, this);
 345     }
 346 
 347     /**
 348      * An AttributeSet that is always empty.
 349      */
 350     static class EmptyAttributeSet implements AttributeSet, Serializable {
 351         static final long serialVersionUID = -8714803568785904228L;
 352 
 353         public int getAttributeCount() {
 354             return 0;
 355         }
 356         public boolean isDefined(Object attrName) {
 357             return false;
 358         }
 359         public boolean isEqual(AttributeSet attr) {
 360             return (attr.getAttributeCount() == 0);
 361         }
 362         public AttributeSet copyAttributes() {
 363             return this;
 364         }
 365         public Object getAttribute(Object key) {
 366             return null;
 367         }
 368         public Enumeration<?> getAttributeNames() {
 369             return Collections.emptyEnumeration();
 370         }
 371         public boolean containsAttribute(Object name, Object value) {
 372             return false;
 373         }
 374         public boolean containsAttributes(AttributeSet attributes) {
 375             return (attributes.getAttributeCount() == 0);
 376         }
 377         public AttributeSet getResolveParent() {
 378             return null;
 379         }
 380         public boolean equals(Object obj) {
 381             if (this == obj) {
 382                 return true;
 383             }
 384             return ((obj instanceof AttributeSet) &&
 385                     (((AttributeSet)obj).getAttributeCount() == 0));
 386         }
 387         public int hashCode() {
 388             return 0;
 389         }
 390     }
 391 }