1 /*
   2  * Copyright (c) 1999, 2020, 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      * @serialData Default field (ignoreCase flag - a {@code boolean}), followed by
 283      * the number of attributes in the set
 284      * (an {@code int}), and then the individual {@code Attribute} objects.
 285      *
 286      * @param s the {@code ObjectOutputStream} to write to
 287      * @throws java.io.IOException if an I/O error occurs.
 288      */
 289     @java.io.Serial
 290     private void writeObject(java.io.ObjectOutputStream s)
 291             throws java.io.IOException {
 292         // Overridden to avoid exposing implementation details
 293         s.defaultWriteObject(); // write out the ignoreCase flag
 294         s.writeInt(attrs.size());
 295         Enumeration<Attribute> attrEnum = attrs.elements();
 296         while (attrEnum.hasMoreElements()) {
 297             s.writeObject(attrEnum.nextElement());
 298         }
 299     }
 300 
 301     /**
 302      * Initializes the {@code BasicAttributes} from deserialized data.
 303      *
 304      * See {@code writeObject} for a description of the serial form.
 305      *
 306      * @param s the {@code ObjectInputStream} to read from
 307      * @throws java.io.IOException if an I/O error occurs.
 308      * @throws ClassNotFoundException if the class of a serialized object
 309      *         could not be found.
 310      */
 311     @java.io.Serial
 312     private void readObject(java.io.ObjectInputStream s)
 313             throws java.io.IOException, ClassNotFoundException {
 314         // Overridden to avoid exposing implementation details.
 315         s.defaultReadObject();  // read in the ignoreCase flag
 316         int n = s.readInt();    // number of attributes
 317         attrs = (n >= 1)
 318                 ? new Hashtable<>(1 + (int) (Math.min(768, n) / .75f))
 319                 : new Hashtable<>(2); // can't have initial size of 0 (grrr...)
 320         while (--n >= 0) {
 321             put((Attribute)s.readObject());
 322         }
 323     }
 324 
 325 
 326 class AttrEnumImpl implements NamingEnumeration<Attribute> {
 327 
 328     Enumeration<Attribute> elements;
 329 
 330     public AttrEnumImpl() {
 331         this.elements = attrs.elements();
 332     }
 333 
 334     public boolean hasMoreElements() {
 335         return elements.hasMoreElements();
 336     }
 337 
 338     public Attribute nextElement() {
 339         return elements.nextElement();
 340     }
 341 
 342     public boolean hasMore() throws NamingException {
 343         return hasMoreElements();
 344     }
 345 
 346     public Attribute next() throws NamingException {
 347         return nextElement();
 348     }
 349 
 350     public void close() throws NamingException {
 351         elements = null;
 352     }
 353 }
 354 
 355 class IDEnumImpl implements NamingEnumeration<String> {
 356 
 357     Enumeration<Attribute> elements;
 358 
 359     public IDEnumImpl() {
 360         // Walking through the elements, rather than the keys, gives
 361         // us attribute IDs that have not been converted to lowercase.
 362         this.elements = attrs.elements();
 363     }
 364 
 365     public boolean hasMoreElements() {
 366         return elements.hasMoreElements();
 367     }
 368 
 369     public String nextElement() {
 370         Attribute attr = elements.nextElement();
 371         return attr.getID();
 372     }
 373 
 374     public boolean hasMore() throws NamingException {
 375         return hasMoreElements();
 376     }
 377 
 378     public String next() throws NamingException {
 379         return nextElement();
 380     }
 381 
 382     public void close() throws NamingException {
 383         elements = null;
 384     }
 385 }
 386 
 387     /**
 388      * Use serialVersionUID from JNDI 1.1.1 for interoperability.
 389      */
 390     private static final long serialVersionUID = 4980164073184639448L;
 391 }