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     public Object clone() {
 276         SimpleAttributeSet attr;
 277         try {
 278             attr = (SimpleAttributeSet) super.clone();
 279             attr.table = (LinkedHashMap) table.clone();
 280         } catch (CloneNotSupportedException cnse) {
 281             attr = null;
 282         }
 283         return attr;
 284     }
 285 
 286     /**
 287      * Returns a hashcode for this set of attributes.
 288      * @return     a hashcode value for this set of attributes.
 289      */
 290     public int hashCode() {
 291         return table.hashCode();
 292     }
 293 
 294     /**
 295      * Compares this object to the specified object.
 296      * The result is <code>true</code> if the object is an equivalent
 297      * set of attributes.
 298      * @param     obj   the object to compare this attribute set with
 299      * @return    <code>true</code> if the objects are equal;
 300      *            <code>false</code> otherwise
 301      */
 302     public boolean equals(Object obj) {
 303         if (this == obj) {
 304             return true;
 305         }
 306         if (obj instanceof AttributeSet) {
 307             AttributeSet attrs = (AttributeSet) obj;
 308             return isEqual(attrs);
 309         }
 310         return false;
 311     }
 312 
 313     /**
 314      * Converts the attribute set to a String.
 315      *
 316      * @return the string
 317      */
 318     public String toString() {
 319         String s = "";
 320         Enumeration names = getAttributeNames();
 321         while (names.hasMoreElements()) {
 322             Object key = names.nextElement();
 323             Object value = getAttribute(key);
 324             if (value instanceof AttributeSet) {
 325                 // don't go recursive
 326                 s = s + key + "=**AttributeSet** ";
 327             } else {
 328                 s = s + key + "=" + value + " ";
 329             }
 330         }
 331         return s;
 332     }
 333 
 334     private void writeObject(java.io.ObjectOutputStream s) throws IOException {
 335         s.defaultWriteObject();
 336         StyleContext.writeAttributeSet(s, this);
 337     }
 338 
 339     private void readObject(ObjectInputStream s)
 340       throws ClassNotFoundException, IOException {
 341         s.defaultReadObject();
 342         table = new LinkedHashMap<>(3);
 343         StyleContext.readAttributeSet(s, this);
 344     }
 345 
 346     /**
 347      * An AttributeSet that is always empty.
 348      */
 349     static class EmptyAttributeSet implements AttributeSet, Serializable {
 350         static final long serialVersionUID = -8714803568785904228L;
 351 
 352         public int getAttributeCount() {
 353             return 0;
 354         }
 355         public boolean isDefined(Object attrName) {
 356             return false;
 357         }
 358         public boolean isEqual(AttributeSet attr) {
 359             return (attr.getAttributeCount() == 0);
 360         }
 361         public AttributeSet copyAttributes() {
 362             return this;
 363         }
 364         public Object getAttribute(Object key) {
 365             return null;
 366         }
 367         public Enumeration getAttributeNames() {
 368             return Collections.emptyEnumeration();
 369         }
 370         public boolean containsAttribute(Object name, Object value) {
 371             return false;
 372         }
 373         public boolean containsAttributes(AttributeSet attributes) {
 374             return (attributes.getAttributeCount() == 0);
 375         }
 376         public AttributeSet getResolveParent() {
 377             return null;
 378         }
 379         public boolean equals(Object obj) {
 380             if (this == obj) {
 381                 return true;
 382             }
 383             return ((obj instanceof AttributeSet) &&
 384                     (((AttributeSet)obj).getAttributeCount() == 0));
 385         }
 386         public int hashCode() {
 387             return 0;
 388         }
 389     }
 390 }