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