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 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     public Object clone() {
 142         BasicAttributes attrset;
 143         try {
 144             attrset = (BasicAttributes)super.clone();
 145         } catch (CloneNotSupportedException e) {
 146             attrset = new BasicAttributes(ignoreCase);
 147         }
 148         attrset.attrs = (Hashtable)attrs.clone();
 149         return attrset;
 150     }
 151 
 152     public boolean isCaseIgnored() {
 153         return ignoreCase;
 154     }
 155 
 156     public int size() {
 157         return attrs.size();
 158     }
 159 
 160     public Attribute get(String attrID) {
 161         Attribute attr = (Attribute) attrs.get(
 162                 ignoreCase ? attrID.toLowerCase() : attrID);
 163         return (attr);
 164     }
 165 
 166     public NamingEnumeration<Attribute> getAll() {
 167         return new AttrEnumImpl();
 168     }
 169 
 170     public NamingEnumeration<String> getIDs() {
 171         return new IDEnumImpl();
 172     }
 173 
 174     public Attribute put(String attrID, Object val) {
 175         return this.put(new BasicAttribute(attrID, val));
 176     }
 177 
 178     public Attribute put(Attribute attr) {
 179         String id = attr.getID();
 180         if (ignoreCase) {
 181             id = id.toLowerCase();
 182         }
 183         return (Attribute)attrs.put(id, attr);
 184     }
 185 
 186     public Attribute remove(String attrID) {
 187         String id = (ignoreCase ? attrID.toLowerCase() : attrID);
 188         return (Attribute)attrs.remove(id);
 189     }
 190 
 191     /**
 192      * Generates the string representation of this attribute set.
 193      * The string consists of each attribute identifier and the contents
 194      * of each attribute. The contents of this string is useful
 195      * for debugging and is not meant to be interpreted programmatically.
 196      *
 197      * @return A non-null string listing the contents of this attribute set.
 198      */
 199     public String toString() {
 200         if (attrs.size() == 0) {
 201             return("No attributes");
 202         } else {
 203             return attrs.toString();
 204         }
 205     }
 206 
 207     /**
 208      * Determines whether this <tt>BasicAttributes</tt> is equal to another
 209      * <tt>Attributes</tt>
 210      * Two <tt>Attributes</tt> are equal if they are both instances of
 211      * <tt>Attributes</tt>,
 212      * treat the case of attribute IDs the same way, and contain the
 213      * same attributes. Each <tt>Attribute</tt> in this <tt>BasicAttributes</tt>
 214      * is checked for equality using <tt>Object.equals()</tt>, which may have
 215      * be overridden by implementations of <tt>Attribute</tt>).
 216      * If a subclass overrides <tt>equals()</tt>,
 217      * it should override <tt>hashCode()</tt>
 218      * as well so that two <tt>Attributes</tt> instances that are equal
 219      * have the same hash code.
 220      * @param obj the possibly null object to compare against.
 221      *
 222      * @return true If obj is equal to this BasicAttributes.
 223      * @see #hashCode
 224      */
 225     public boolean equals(Object obj) {
 226         if ((obj != null) && (obj instanceof Attributes)) {
 227             Attributes target = (Attributes)obj;
 228 
 229             // Check case first
 230             if (ignoreCase != target.isCaseIgnored()) {
 231                 return false;
 232             }
 233 
 234             if (size() == target.size()) {
 235                 Attribute their, mine;
 236                 try {
 237                     NamingEnumeration theirs = target.getAll();
 238                     while (theirs.hasMore()) {
 239                         their = (Attribute)theirs.next();
 240                         mine = get(their.getID());
 241                         if (!their.equals(mine)) {
 242                             return false;
 243                         }
 244                     }
 245                 } catch (NamingException e) {
 246                     return false;
 247                 }
 248                 return true;
 249             }
 250         }
 251         return false;
 252     }
 253 
 254     /**
 255      * Calculates the hash code of this BasicAttributes.
 256      *<p>
 257      * The hash code is computed by adding the hash code of
 258      * the attributes of this object. If this BasicAttributes
 259      * ignores case of its attribute IDs, one is added to the hash code.
 260      * If a subclass overrides <tt>hashCode()</tt>,
 261      * it should override <tt>equals()</tt>
 262      * as well so that two <tt>Attributes</tt> instances that are equal
 263      * have the same hash code.
 264      *
 265      * @return an int representing the hash code of this BasicAttributes instance.
 266      * @see #equals
 267      */
 268     public int hashCode() {
 269         int hash = (ignoreCase ? 1 : 0);
 270         try {
 271             NamingEnumeration all = getAll();
 272             while (all.hasMore()) {
 273                 hash += all.next().hashCode();
 274             }
 275         } catch (NamingException e) {}
 276         return hash;
 277     }
 278 
 279     /**
 280      * Overridden to avoid exposing implementation details.
 281      * @serialData Default field (ignoreCase flag -- a boolean), followed by
 282      * the number of attributes in the set
 283      * (an int), and then the individual Attribute objects.
 284      */
 285     private void writeObject(java.io.ObjectOutputStream s)
 286             throws java.io.IOException {
 287         s.defaultWriteObject(); // write out the ignoreCase flag
 288         s.writeInt(attrs.size());
 289         Enumeration attrEnum = attrs.elements();
 290         while (attrEnum.hasMoreElements()) {
 291             s.writeObject(attrEnum.nextElement());
 292         }
 293     }
 294 
 295     /**
 296      * Overridden to avoid exposing implementation details.
 297      */
 298     private void readObject(java.io.ObjectInputStream s)
 299             throws java.io.IOException, ClassNotFoundException {
 300         s.defaultReadObject();  // read in the ignoreCase flag
 301         int n = s.readInt();    // number of attributes
 302         attrs = (n >= 1)
 303             ? new Hashtable(n * 2)
 304             : new Hashtable(2); // can't have initial size of 0 (grrr...)
 305         while (--n >= 0) {
 306             put((Attribute)s.readObject());
 307         }
 308     }
 309 
 310 
 311 class AttrEnumImpl implements NamingEnumeration<Attribute> {
 312 
 313     Enumeration<Attribute> elements;
 314 
 315     public AttrEnumImpl() {
 316         this.elements = attrs.elements();
 317     }
 318 
 319     public boolean hasMoreElements() {
 320         return elements.hasMoreElements();
 321     }
 322 
 323     public Attribute nextElement() {
 324         return elements.nextElement();
 325     }
 326 
 327     public boolean hasMore() throws NamingException {
 328         return hasMoreElements();
 329     }
 330 
 331     public Attribute next() throws NamingException {
 332         return nextElement();
 333     }
 334 
 335     public void close() throws NamingException {
 336         elements = null;
 337     }
 338 }
 339 
 340 class IDEnumImpl implements NamingEnumeration<String> {
 341 
 342     Enumeration<Attribute> elements;
 343 
 344     public IDEnumImpl() {
 345         // Walking through the elements, rather than the keys, gives
 346         // us attribute IDs that have not been converted to lowercase.
 347         this.elements = attrs.elements();
 348     }
 349 
 350     public boolean hasMoreElements() {
 351         return elements.hasMoreElements();
 352     }
 353 
 354     public String nextElement() {
 355         Attribute attr = elements.nextElement();
 356         return attr.getID();
 357     }
 358 
 359     public boolean hasMore() throws NamingException {
 360         return hasMoreElements();
 361     }
 362 
 363     public String next() throws NamingException {
 364         return nextElement();
 365     }
 366 
 367     public void close() throws NamingException {
 368         elements = null;
 369     }
 370 }
 371 
 372     /**
 373      * Use serialVersionUID from JNDI 1.1.1 for interoperability.
 374      */
 375     private static final long serialVersionUID = 4980164073184639448L;
 376 }