1 /*
   2  * Copyright (c) 2000, 2017, 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 
  26 package javax.print.attribute;
  27 
  28 import java.io.IOException;
  29 import java.io.ObjectInputStream;
  30 import java.io.ObjectOutputStream;
  31 import java.io.Serializable;
  32 import java.util.HashMap;
  33 
  34 /**
  35  * Class {@code HashAttributeSet} provides an {@code AttributeSet}
  36  * implementation with characteristics of a hash map.
  37  *
  38  * @author Alan Kaminsky
  39  */
  40 public class HashAttributeSet implements AttributeSet, Serializable {
  41 
  42     /**
  43      * Use serialVersionUID from JDK 1.4 for interoperability.
  44      */
  45     private static final long serialVersionUID = 5311560590283707917L;
  46 
  47     /**
  48      * The interface of which all members of this attribute set must be an
  49      * instance. It is assumed to be interface {@link Attribute Attribute} or a
  50      * subinterface thereof.
  51      *
  52      * @serial
  53      */
  54     private Class<?> myInterface;
  55 
  56     /**
  57      * A {@code HashMap} used by the implementation. The serialised form doesn't
  58      * include this instance variable.
  59      */
  60     private transient HashMap<Class<?>, Attribute> attrMap = new HashMap<>();
  61 
  62     /**
  63      * Write the instance to a stream (ie serialize the object).
  64      *
  65      * @param  s the output stream
  66      * @throws IOException if an I/O exception has occurred
  67      * @serialData The serialized form of an attribute set explicitly writes the
  68      *             number of attributes in the set, and each of the attributes.
  69      *             This does not guarantee equality of serialized forms since
  70      *             the order in which the attributes are written is not defined.
  71      */
  72     private void writeObject(ObjectOutputStream s) throws IOException {
  73 
  74         s.defaultWriteObject();
  75         Attribute [] attrs = toArray();
  76         s.writeInt(attrs.length);
  77         for (int i = 0; i < attrs.length; i++) {
  78             s.writeObject(attrs[i]);
  79         }
  80     }
  81 
  82     /**
  83      * Reconstitute an instance from a stream that is, deserialize it).
  84      *
  85      * @param  s the input stream
  86      * @throws ClassNotFoundException if the class is not found
  87      * @throws IOException if an I/O exception has occurred
  88      */
  89     private void readObject(ObjectInputStream s)
  90         throws ClassNotFoundException, IOException {
  91 
  92         s.defaultReadObject();
  93         attrMap = new HashMap<>();
  94         int count = s.readInt();
  95         Attribute attr;
  96         for (int i = 0; i < count; i++) {
  97             attr = (Attribute)s.readObject();
  98             add(attr);
  99         }
 100     }
 101 
 102     /**
 103      * Construct a new, empty attribute set.
 104      */
 105     public HashAttributeSet() {
 106         this(Attribute.class);
 107     }
 108 
 109     /**
 110      * Construct a new attribute set, initially populated with the given
 111      * attribute.
 112      *
 113      * @param  attribute attribute value to add to the set
 114      * @throws NullPointerException if {@code attribute} is {@code null}
 115      */
 116     public HashAttributeSet(Attribute attribute) {
 117         this (attribute, Attribute.class);
 118     }
 119 
 120     /**
 121      * Construct a new attribute set, initially populated with the values from
 122      * the given array. The new attribute set is populated by adding the
 123      * elements of {@code attributes} array to the set in sequence, starting at
 124      * index 0. Thus, later array elements may replace earlier array elements if
 125      * the array contains duplicate attribute values or attribute categories.
 126      *
 127      * @param  attributes array of attribute values to add to the set. If
 128      *         {@code null}, an empty attribute set is constructed.
 129      * @throws NullPointerException if any element of {@code attributes} is
 130      *         {@code null}
 131      */
 132     public HashAttributeSet(Attribute[] attributes) {
 133         this (attributes, Attribute.class);
 134     }
 135 
 136     /**
 137      * Construct a new attribute set, initially populated with the values from
 138      * the given set.
 139      *
 140      * @param  attributes set of attributes from which to initialise this set.
 141      *         If {@code null}, an empty attribute set is constructed.
 142      */
 143     public HashAttributeSet(AttributeSet attributes) {
 144         this (attributes, Attribute.class);
 145     }
 146 
 147     /**
 148      * Construct a new, empty attribute set, where the members of the attribute
 149      * set are restricted to the given interface.
 150      *
 151      * @param  interfaceName the interface of which all members of this
 152      *         attribute set must be an instance. It is assumed to be interface
 153      *         {@link Attribute Attribute} or a subinterface thereof.
 154      * @throws NullPointerException if {@code interfaceName} is {@code null}
 155      */
 156     protected HashAttributeSet(Class<?> interfaceName) {
 157         if (interfaceName == null) {
 158             throw new NullPointerException("null interface");
 159         }
 160         myInterface = interfaceName;
 161     }
 162 
 163     /**
 164      * Construct a new attribute set, initially populated with the given
 165      * attribute, where the members of the attribute set are restricted to the
 166      * given interface.
 167      *
 168      * @param  attribute attribute value to add to the set
 169      * @param  interfaceName the interface of which all members of this
 170      *         attribute set must be an instance. It is assumed to be interface
 171      *         {@link Attribute Attribute} or a subinterface thereof.
 172      * @throws NullPointerException if {@code attribute} or
 173      *         {@code interfaceName} are {@code null}
 174      * @throws ClassCastException if {@code attribute} is not an instance of
 175      *         {@code interfaceName}
 176      */
 177     protected HashAttributeSet(Attribute attribute, Class<?> interfaceName) {
 178         if (interfaceName == null) {
 179             throw new NullPointerException("null interface");
 180         }
 181         myInterface = interfaceName;
 182         add (attribute);
 183     }
 184 
 185     /**
 186      * Construct a new attribute set, where the members of the attribute set are
 187      * restricted to the given interface. The new attribute set is populated by
 188      * adding the elements of {@code attributes} array to the set in sequence,
 189      * starting at index 0. Thus, later array elements may replace earlier array
 190      * elements if the array contains duplicate attribute values or attribute
 191      * categories.
 192      *
 193      * @param  attributes array of attribute values to add to the set. If
 194      *         {@code null}, an empty attribute set is constructed.
 195      * @param  interfaceName the interface of which all members of this
 196      *         attribute set must be an instance. It is assumed to be interface
 197      *         {@link Attribute Attribute} or a subinterface thereof.
 198      * @throws NullPointerException if {@code interfaceName} is {@code null}, or
 199      *         if any element of {@code attributes} is {@code null}
 200      * @throws ClassCastException if any element of {@code attributes} is not an
 201      *         instance of {@code interfaceName}
 202      */
 203     protected HashAttributeSet(Attribute[] attributes, Class<?> interfaceName) {
 204         if (interfaceName == null) {
 205             throw new NullPointerException("null interface");
 206         }
 207         myInterface = interfaceName;
 208         int n = attributes == null ? 0 : attributes.length;
 209         for (int i = 0; i < n; ++ i) {
 210             add (attributes[i]);
 211         }
 212     }
 213 
 214     /**
 215      * Construct a new attribute set, initially populated with the values from
 216      * the given set where the members of the attribute set are restricted to
 217      * the given interface.
 218      *
 219      * @param  attributes set of attribute values to initialise the set. If
 220      *         {@code null}, an empty attribute set is constructed.
 221      * @param  interfaceName The interface of which all members of this
 222      *         attribute set must be an instance. It is assumed to be interface
 223      *         {@link Attribute Attribute} or a subinterface thereof.
 224      * @throws ClassCastException if any element of {@code attributes} is not an
 225      *         instance of {@code interfaceName}
 226      */
 227     protected HashAttributeSet(AttributeSet attributes, Class<?> interfaceName) {
 228       myInterface = interfaceName;
 229       if (attributes != null) {
 230         Attribute[] attribArray = attributes.toArray();
 231         int n = attribArray == null ? 0 : attribArray.length;
 232         for (int i = 0; i < n; ++ i) {
 233           add (attribArray[i]);
 234         }
 235       }
 236     }
 237 
 238     /**
 239      * Returns the attribute value which this attribute set contains in the
 240      * given attribute category. Returns {@code null} if this attribute set does
 241      * not contain any attribute value in the given attribute category.
 242      *
 243      * @param  category attribute category whose associated attribute value is
 244      *         to be returned. It must be a {@link Class Class} that implements
 245      *         interface {@link Attribute Attribute}.
 246      * @return the attribute value in the given attribute category contained in
 247      *         this attribute set, or {@code null} if this attribute set does
 248      *         not contain any attribute value in the given attribute category
 249      * @throws NullPointerException if the {@code category} is {@code null}
 250      * @throws ClassCastException if the {@code category} is not a
 251      *         {@link Class Class} that implements interface
 252      *         {@link Attribute Attribute}
 253      */
 254     public Attribute get(Class<?> category) {
 255         return attrMap.get(AttributeSetUtilities.
 256                            verifyAttributeCategory(category,
 257                                                    Attribute.class));
 258     }
 259 
 260     /**
 261      * Adds the specified attribute to this attribute set if it is not already
 262      * present, first removing any existing in the same attribute category as
 263      * the specified attribute value.
 264      *
 265      * @param  attribute attribute value to be added to this attribute set
 266      * @return {@code true} if this attribute set changed as a result of the
 267      *         call, i.e., the given attribute value was not already a member of
 268      *         this attribute set
 269      * @throws NullPointerException if the {@code attribute} is {@code null}
 270      * @throws UnmodifiableSetException if this attribute set does not support
 271      *         the {@code add()} operation
 272      */
 273     public boolean add(Attribute attribute) {
 274         Object oldAttribute =
 275             attrMap.put(attribute.getCategory(),
 276                         AttributeSetUtilities.
 277                         verifyAttributeValue(attribute, myInterface));
 278         return (!attribute.equals(oldAttribute));
 279     }
 280 
 281     /**
 282      * Removes any attribute for this category from this attribute set if
 283      * present. If {@code category} is {@code null}, then {@code remove()} does
 284      * nothing and returns {@code false}.
 285      *
 286      * @param  category attribute category to be removed from this attribute set
 287      * @return {@code true} if this attribute set changed as a result of the
 288      *         call, i.e., the given attribute category had been a member of
 289      *         this attribute set
 290      * @throws UnmodifiableSetException if this attribute set does not support
 291      *         the {@code remove()} operation
 292      */
 293     public boolean remove(Class<?> category) {
 294         return
 295             category != null &&
 296             AttributeSetUtilities.
 297             verifyAttributeCategory(category, Attribute.class) != null &&
 298             attrMap.remove(category) != null;
 299     }
 300 
 301     /**
 302      * Removes the specified attribute from this attribute set if present. If
 303      * {@code attribute} is {@code null}, then {@code remove()} does nothing and
 304      * returns {@code false}.
 305      *
 306      * @param  attribute attribute value to be removed from this attribute set
 307      * @return {@code true} if this attribute set changed as a result of the
 308      *         call, i.e., the given attribute value had been a member of this
 309      *         attribute set
 310      * @throws UnmodifiableSetException if this attribute set does not support
 311      *         the {@code remove()} operation
 312      */
 313     public boolean remove(Attribute attribute) {
 314         return
 315             attribute != null &&
 316             attrMap.remove(attribute.getCategory()) != null;
 317     }
 318 
 319     /**
 320      * Returns {@code true} if this attribute set contains an attribute for the
 321      * specified category.
 322      *
 323      * @param  category whose presence in this attribute set is to be tested
 324      * @return {@code true} if this attribute set contains an attribute value
 325      *         for the specified category
 326      */
 327     public boolean containsKey(Class<?> category) {
 328         return
 329             category != null &&
 330             AttributeSetUtilities.
 331             verifyAttributeCategory(category, Attribute.class) != null &&
 332             attrMap.get(category) != null;
 333     }
 334 
 335     /**
 336      * Returns {@code true} if this attribute set contains the given attribute.
 337      *
 338      * @param  attribute value whose presence in this attribute set is to be
 339      *         tested
 340      * @return {@code true} if this attribute set contains the given attribute
 341      *         value
 342      */
 343     public boolean containsValue(Attribute attribute) {
 344         return
 345            attribute != null &&
 346            attribute instanceof Attribute &&
 347            attribute.equals(attrMap.get(attribute.getCategory()));
 348     }
 349 
 350     /**
 351      * Adds all of the elements in the specified set to this attribute. The
 352      * outcome is the same as if the {@link #add(Attribute) add(Attribute)}
 353      * operation had been applied to this attribute set successively with each
 354      * element from the specified set. The behavior of the
 355      * {@code addAll(AttributeSet)} operation is unspecified if the specified
 356      * set is modified while the operation is in progress.
 357      * <p>
 358      * If the {@code addAll(AttributeSet)} operation throws an exception, the
 359      * effect on this attribute set's state is implementation dependent;
 360      * elements from the specified set before the point of the exception may or
 361      * may not have been added to this attribute set.
 362      *
 363      * @param  attributes whose elements are to be added to this attribute set
 364      * @return {@code true} if this attribute set changed as a result of the
 365      *         call
 366      * @throws UnmodifiableSetException if this attribute set does not support
 367      *         the {@code addAll(AttributeSet)} method
 368      * @throws NullPointerException if some element in the specified set is
 369      *         {@code null}, or the set is {@code null}
 370      * @see #add(Attribute)
 371      */
 372     public boolean addAll(AttributeSet attributes) {
 373 
 374         Attribute []attrs = attributes.toArray();
 375         boolean result = false;
 376         for (int i=0; i<attrs.length; i++) {
 377             Attribute newValue =
 378                 AttributeSetUtilities.verifyAttributeValue(attrs[i],
 379                                                            myInterface);
 380             Object oldValue = attrMap.put(newValue.getCategory(), newValue);
 381             result = (! newValue.equals(oldValue)) || result;
 382         }
 383         return result;
 384     }
 385 
 386     /**
 387      * Returns the number of attributes in this attribute set. If this attribute
 388      * set contains more than {@code Integer.MAX_VALUE} elements, returns
 389      * {@code Integer.MAX_VALUE}.
 390      *
 391      * @return the number of attributes in this attribute set
 392      */
 393     public int size() {
 394         return attrMap.size();
 395     }
 396 
 397     /**
 398      * Returns an array of the attributes contained in this set.
 399      *
 400      * @return the attributes contained in this set as an array, zero length if
 401      *         the {@code AttributeSet} is empty
 402      */
 403     public Attribute[] toArray() {
 404         Attribute []attrs = new Attribute[size()];
 405         attrMap.values().toArray(attrs);
 406         return attrs;
 407     }
 408 
 409     /**
 410      * Removes all attributes from this attribute set.
 411      *
 412      * @throws UnmodifiableSetException if this attribute set does not support
 413      *         the {@code clear()} operation
 414      */
 415     public void clear() {
 416         attrMap.clear();
 417     }
 418 
 419     /**
 420      * Returns {@code true} if this attribute set contains no attributes.
 421      *
 422      * @return {@code true} if this attribute set contains no attributes
 423      */
 424     public boolean isEmpty() {
 425         return attrMap.isEmpty();
 426     }
 427 
 428     /**
 429      * Compares the specified object with this attribute set for equality.
 430      * Returns {@code true} if the given object is also an attribute set and the
 431      * two attribute sets contain the same attribute category-attribute value
 432      * mappings. This ensures that the {@code equals()} method works properly
 433      * across different implementations of the {@code AttributeSet} interface.
 434      *
 435      * @param  object to be compared for equality with this attribute set
 436      * @return {@code true} if the specified object is equal to this attribute
 437      *         set
 438      */
 439     public boolean equals(Object object) {
 440         if (object == null || !(object instanceof AttributeSet)) {
 441             return false;
 442         }
 443 
 444         AttributeSet aset = (AttributeSet)object;
 445         if (aset.size() != size()) {
 446             return false;
 447         }
 448 
 449         Attribute[] attrs = toArray();
 450         for (int i=0;i<attrs.length; i++) {
 451             if (!aset.containsValue(attrs[i])) {
 452                 return false;
 453             }
 454         }
 455         return true;
 456     }
 457 
 458     /**
 459      * Returns the hash code value for this attribute set. The hash code of an
 460      * attribute set is defined to be the sum of the hash codes of each entry in
 461      * the {@code AttributeSet}. This ensures that {@code t1.equals(t2)} implies
 462      * that {@code t1.hashCode()==t2.hashCode()} for any two attribute sets
 463      * {@code t1} and {@code t2}, as required by the general contract of
 464      * {@link Object#hashCode() Object.hashCode()}.
 465      *
 466      * @return the hash code value for this attribute set
 467      */
 468     public int hashCode() {
 469         int hcode = 0;
 470         Attribute[] attrs = toArray();
 471         for (int i=0;i<attrs.length; i++) {
 472             hcode += attrs[i].hashCode();
 473         }
 474         return hcode;
 475     }
 476 }