1 /*
   2  * Copyright (c) 1999, 2004, 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 
  27 package javax.naming.directory;
  28 
  29 import java.util.Hashtable;
  30 import java.util.Enumeration;
  31 
  32 import javax.naming.NamingException;
  33 import javax.naming.NamingEnumeration;
  34 
  35 /**
  36   * This class provides a basic implementation
  37   * of the Attributes interface.
  38   *<p>
  39   * BasicAttributes is either case-sensitive or case-insensitive (case-ignore).
  40   * This property is determined at the time the BasicAttributes constructor
  41   * is called.
  42   * In a case-insensitive BasicAttributes, the case of its attribute identifiers
  43   * is ignored when searching for an attribute, or adding attributes.
  44   * In a case-sensitive BasicAttributes, the case is significant.
  45   *<p>
  46   * When the BasicAttributes class needs to create an Attribute, it
  47   * uses BasicAttribute. There is no other dependency on BasicAttribute.
  48   *<p>
  49   * Note that updates to BasicAttributes (such as adding or removing an attribute)
  50   * does not affect the corresponding representation in the directory.
  51   * Updates to the directory can only be effected
  52   * using operations in the DirContext interface.
  53   *<p>
  54   * A BasicAttributes instance is not synchronized against concurrent
  55   * multithreaded access. Multiple threads trying to access and modify
  56   * a single BasicAttributes instance should lock the object.
  57   *
  58   * @author Rosanna Lee
  59   * @author Scott Seligman
  60   *
  61   * @see DirContext#getAttributes
  62   * @see DirContext#modifyAttributes
  63   * @see DirContext#bind
  64   * @see DirContext#rebind
  65   * @see DirContext#createSubcontext
  66   * @see DirContext#search
  67   * @since 1.3
  68   */
  69 
  70 public class BasicAttributes implements Attributes {
  71     /**
  72      * Indicates whether case of attribute ids is ignored.
  73      * @serial
  74      */
  75     private boolean ignoreCase = false;
  76 
  77     // The 'key' in attrs is stored in the 'right case'.
  78     // If ignoreCase is true, key is aways lowercase.
  79     // If ignoreCase is false, key is stored as supplied by put().
  80     // %%% Not declared "private" due to bug 4064984.
  81     transient Hashtable<String,Attribute> attrs = new Hashtable<>(11);
  82 
  83     /**
  84       * Constructs a new instance of Attributes.
  85       * The character case of attribute identifiers
  86       * is significant when subsequently retrieving or adding attributes.
  87       */
  88     public BasicAttributes() {
  89     }
  90 
  91     /**
  92       * Constructs a new instance of Attributes.
  93       * If <code>ignoreCase</code> is true, the character case of attribute
  94       * identifiers is ignored; otherwise the case is significant.
  95       * @param ignoreCase true means this attribute set will ignore
  96       *                   the case of its attribute identifiers
  97       *                   when retrieving or adding attributes;
  98       *                   false means case is respected.
  99       */
 100     public BasicAttributes(boolean ignoreCase) {
 101         this.ignoreCase = ignoreCase;
 102     }
 103 
 104     /**
 105       * Constructs a new instance of Attributes with one attribute.
 106       * The attribute specified by attrID and val are added to the newly
 107       * created attribute.
 108       * The character case of attribute identifiers
 109       * is significant when subsequently retrieving or adding attributes.
 110       * @param attrID   non-null The id of the attribute to add.
 111       * @param val The value of the attribute to add. If null, a null
 112       *        value is added to the attribute.
 113       */
 114     public BasicAttributes(String attrID, Object val) {
 115         this();
 116         this.put(new BasicAttribute(attrID, val));
 117     }
 118 
 119     /**
 120       * Constructs a new instance of Attributes with one attribute.
 121       * The attribute specified by attrID and val are added to the newly
 122       * created attribute.
 123       * If <code>ignoreCase</code> is true, the character case of attribute
 124       * identifiers is ignored; otherwise the case is significant.
 125       * @param attrID   non-null The id of the attribute to add.
 126       *           If this attribute set ignores the character
 127       *           case of its attribute ids, the case of attrID
 128       *           is ignored.
 129       * @param val The value of the attribute to add. If null, a null
 130       *        value is added to the attribute.
 131       * @param ignoreCase true means this attribute set will ignore
 132       *                   the case of its attribute identifiers
 133       *                   when retrieving or adding attributes;
 134       *                   false means case is respected.
 135       */
 136     public BasicAttributes(String attrID, Object val, boolean ignoreCase) {
 137         this(ignoreCase);
 138         this.put(new BasicAttribute(attrID, val));
 139     }
 140 
 141     @SuppressWarnings("unchecked")
 142     public Object clone() {
 143         BasicAttributes attrset;
 144         try {
 145             attrset = (BasicAttributes)super.clone();
 146         } catch (CloneNotSupportedException e) {
 147             attrset = new BasicAttributes(ignoreCase);
 148         }
 149         attrset.attrs = (Hashtable<String,Attribute>)attrs.clone();
 150         return attrset;
 151     }
 152 
 153     public boolean isCaseIgnored() {
 154         return ignoreCase;
 155     }
 156 
 157     public int size() {
 158         return attrs.size();
 159     }
 160 
 161     public Attribute get(String attrID) {
 162         Attribute attr = attrs.get(
 163                 ignoreCase ? attrID.toLowerCase() : attrID);
 164         return (attr);
 165     }
 166 
 167     public NamingEnumeration<Attribute> getAll() {
 168         return new AttrEnumImpl();
 169     }
 170 
 171     public NamingEnumeration<String> getIDs() {
 172         return new IDEnumImpl();
 173     }
 174 
 175     public Attribute put(String attrID, Object val) {
 176         return this.put(new BasicAttribute(attrID, val));
 177     }
 178 
 179     public Attribute put(Attribute attr) {
 180         String id = attr.getID();
 181         if (ignoreCase) {
 182             id = id.toLowerCase();
 183         }
 184         return attrs.put(id, attr);
 185     }
 186 
 187     public Attribute remove(String attrID) {
 188         String id = (ignoreCase ? attrID.toLowerCase() : attrID);
 189         return attrs.remove(id);
 190     }
 191 
 192     /**
 193      * Generates the string representation of this attribute set.
 194      * The string consists of each attribute identifier and the contents
 195      * of each attribute. The contents of this string is useful
 196      * for debugging and is not meant to be interpreted programmatically.
 197      *
 198      * @return A non-null string listing the contents of this attribute set.
 199      */
 200     public String toString() {
 201         if (attrs.size() == 0) {
 202             return("No attributes");
 203         } else {
 204             return attrs.toString();
 205         }
 206     }
 207 
 208     /**
 209      * Determines whether this <tt>BasicAttributes</tt> is equal to another
 210      * <tt>Attributes</tt>
 211      * Two <tt>Attributes</tt> are equal if they are both instances of
 212      * <tt>Attributes</tt>,
 213      * treat the case of attribute IDs the same way, and contain the
 214      * same attributes. Each <tt>Attribute</tt> in this <tt>BasicAttributes</tt>
 215      * is checked for equality using <tt>Object.equals()</tt>, which may have
 216      * be overridden by implementations of <tt>Attribute</tt>).
 217      * If a subclass overrides <tt>equals()</tt>,
 218      * it should override <tt>hashCode()</tt>
 219      * as well so that two <tt>Attributes</tt> instances that are equal
 220      * have the same hash code.
 221      * @param obj the possibly null object to compare against.
 222      *
 223      * @return true If obj is equal to this BasicAttributes.
 224      * @see #hashCode
 225      */
 226     public boolean equals(Object obj) {
 227         if ((obj != null) && (obj instanceof Attributes)) {
 228             Attributes target = (Attributes)obj;
 229 
 230             // Check case first
 231             if (ignoreCase != target.isCaseIgnored()) {
 232                 return false;
 233             }
 234 
 235             if (size() == target.size()) {
 236                 Attribute their, mine;
 237                 try {
 238                     NamingEnumeration<?> theirs = target.getAll();
 239                     while (theirs.hasMore()) {
 240                         their = (Attribute)theirs.next();
 241                         mine = get(their.getID());
 242                         if (!their.equals(mine)) {
 243                             return false;
 244                         }
 245                     }
 246                 } catch (NamingException e) {
 247                     return false;
 248                 }
 249                 return true;
 250             }
 251         }
 252         return false;
 253     }
 254 
 255     /**
 256      * Calculates the hash code of this BasicAttributes.
 257      *<p>
 258      * The hash code is computed by adding the hash code of
 259      * the attributes of this object. If this BasicAttributes
 260      * ignores case of its attribute IDs, one is added to the hash code.
 261      * If a subclass overrides <tt>hashCode()</tt>,
 262      * it should override <tt>equals()</tt>
 263      * as well so that two <tt>Attributes</tt> instances that are equal
 264      * have the same hash code.
 265      *
 266      * @return an int representing the hash code of this BasicAttributes instance.
 267      * @see #equals
 268      */
 269     public int hashCode() {
 270         int hash = (ignoreCase ? 1 : 0);
 271         try {
 272             NamingEnumeration<?> all = getAll();
 273             while (all.hasMore()) {
 274                 hash += all.next().hashCode();
 275             }
 276         } catch (NamingException e) {}
 277         return hash;
 278     }
 279 
 280     /**
 281      * Overridden to avoid exposing implementation details.
 282      * @serialData Default field (ignoreCase flag -- a boolean), followed by
 283      * the number of attributes in the set
 284      * (an int), and then the individual Attribute objects.
 285      */
 286     private void writeObject(java.io.ObjectOutputStream s)
 287             throws java.io.IOException {
 288         s.defaultWriteObject(); // write out the ignoreCase flag
 289         s.writeInt(attrs.size());
 290         Enumeration<Attribute> attrEnum = attrs.elements();
 291         while (attrEnum.hasMoreElements()) {
 292             s.writeObject(attrEnum.nextElement());
 293         }
 294     }
 295 
 296     /**
 297      * Overridden to avoid exposing implementation details.
 298      */
 299     private void readObject(java.io.ObjectInputStream s)
 300             throws java.io.IOException, ClassNotFoundException {
 301         s.defaultReadObject();  // read in the ignoreCase flag
 302         int n = s.readInt();    // number of attributes
 303         attrs = (n >= 1)
 304             ? new Hashtable<String,Attribute>(n * 2)
 305             : new Hashtable<String,Attribute>(2); // can't have initial size of 0 (grrr...)
 306         while (--n >= 0) {
 307             put((Attribute)s.readObject());
 308         }
 309     }
 310 
 311 
 312 class AttrEnumImpl implements NamingEnumeration<Attribute> {
 313 
 314     Enumeration<Attribute> elements;
 315 
 316     public AttrEnumImpl() {
 317         this.elements = attrs.elements();
 318     }
 319 
 320     public boolean hasMoreElements() {
 321         return elements.hasMoreElements();
 322     }
 323 
 324     public Attribute nextElement() {
 325         return elements.nextElement();
 326     }
 327 
 328     public boolean hasMore() throws NamingException {
 329         return hasMoreElements();
 330     }
 331 
 332     public Attribute next() throws NamingException {
 333         return nextElement();
 334     }
 335 
 336     public void close() throws NamingException {
 337         elements = null;
 338     }
 339 }
 340 
 341 class IDEnumImpl implements NamingEnumeration<String> {
 342 
 343     Enumeration<Attribute> elements;
 344 
 345     public IDEnumImpl() {
 346         // Walking through the elements, rather than the keys, gives
 347         // us attribute IDs that have not been converted to lowercase.
 348         this.elements = attrs.elements();
 349     }
 350 
 351     public boolean hasMoreElements() {
 352         return elements.hasMoreElements();
 353     }
 354 
 355     public String nextElement() {
 356         Attribute attr = elements.nextElement();
 357         return attr.getID();
 358     }
 359 
 360     public boolean hasMore() throws NamingException {
 361         return hasMoreElements();
 362     }
 363 
 364     public String next() throws NamingException {
 365         return nextElement();
 366     }
 367 
 368     public void close() throws NamingException {
 369         elements = null;
 370     }
 371 }
 372 
 373     /**
 374      * Use serialVersionUID from JNDI 1.1.1 for interoperability.
 375      */
 376     private static final long serialVersionUID = 4980164073184639448L;
 377 }