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