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