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 package javax.naming.directory;
  27 
  28 import java.util.Vector;
  29 import java.util.Enumeration;
  30 import java.util.NoSuchElementException;
  31 import java.lang.reflect.Array;
  32 
  33 import javax.naming.NamingException;
  34 import javax.naming.NamingEnumeration;
  35 import javax.naming.OperationNotSupportedException;
  36 
  37 /**
  38   * This class provides a basic implementation of the {@code Attribute} interface.
  39   *<p>
  40   * This implementation does not support the schema methods
  41   * {@code getAttributeDefinition()} and {@code getAttributeSyntaxDefinition()}.
  42   * They simply throw {@code OperationNotSupportedException}.
  43   * Subclasses of {@code BasicAttribute} should override these methods if they
  44   * support them.
  45   *<p>
  46   * The {@code BasicAttribute} class by default uses {@code Object.equals()} to
  47   * determine equality of attribute values when testing for equality or
  48   * when searching for values, <em>except</em> when the value is an array.
  49   * For an array, each element of the array is checked using {@code Object.equals()}.
  50   * Subclasses of {@code BasicAttribute} can make use of schema information
  51   * when doing similar equality checks by overriding methods
  52   * in which such use of schema is meaningful.
  53   * Similarly, the {@code BasicAttribute} class by default returns the values passed to its
  54   * constructor and/or manipulated using the add/remove methods.
  55   * Subclasses of {@code BasicAttribute} can override {@code get()} and {@code getAll()}
  56   * to get the values dynamically from the directory (or implement
  57   * the {@code Attribute} interface directly instead of subclassing {@code BasicAttribute}).
  58   *<p>
  59   * Note that updates to {@code BasicAttribute} (such as adding or removing a value)
  60   * does not affect the corresponding representation of the attribute
  61   * in the directory.  Updates to the directory can only be effected
  62   * using operations in the {@code DirContext} interface.
  63   *<p>
  64   * A {@code BasicAttribute} instance is not synchronized against concurrent
  65   * multithreaded access. Multiple threads trying to access and modify a
  66   * {@code BasicAttribute} should lock the object.
  67   *
  68   * @author Rosanna Lee
  69   * @author Scott Seligman
  70   * @since 1.3
  71   */
  72 public class BasicAttribute implements Attribute {
  73     /**
  74      * Holds the attribute's id. It is initialized by the public constructor and
  75      * cannot be null unless methods in BasicAttribute that use attrID
  76      * have been overridden.
  77      * @serial
  78      */
  79     protected String attrID;
  80 
  81     /**
  82      * Holds the attribute's values. Initialized by public constructors.
  83      * Cannot be null unless methods in BasicAttribute that use
  84      * values have been overridden.
  85      */
  86     protected transient Vector<Object> values;
  87 
  88     /**
  89      * A flag for recording whether this attribute's values are ordered.
  90      * @serial
  91      */
  92     protected boolean ordered = false;
  93 
  94     @SuppressWarnings("unchecked")
  95     public Object clone() {
  96         BasicAttribute attr;
  97         try {
  98             attr = (BasicAttribute)super.clone();
  99         } catch (CloneNotSupportedException e) {
 100             attr = new BasicAttribute(attrID, ordered);
 101         }
 102         attr.values = (Vector<Object>)values.clone();
 103         return attr;
 104     }
 105 
 106     /**
 107       * Determines whether obj is equal to this attribute.
 108       * Two attributes are equal if their attribute-ids, syntaxes
 109       * and values are equal.
 110       * If the attribute values are unordered, the order that the values were added
 111       * are irrelevant. If the attribute values are ordered, then the
 112       * order the values must match.
 113       * If obj is null or not an Attribute, false is returned.
 114       *<p>
 115       * By default {@code Object.equals()} is used when comparing the attribute
 116       * id and its values except when a value is an array. For an array,
 117       * each element of the array is checked using {@code Object.equals()}.
 118       * A subclass may override this to make
 119       * use of schema syntax information and matching rules,
 120       * which define what it means for two attributes to be equal.
 121       * How and whether a subclass makes
 122       * use of the schema information is determined by the subclass.
 123       * If a subclass overrides {@code equals()}, it should also override
 124       * {@code hashCode()}
 125       * such that two attributes that are equal have the same hash code.
 126       *
 127       * @param obj      The possibly null object to check.
 128       * @return true if obj is equal to this attribute; false otherwise.
 129       * @see #hashCode
 130       * @see #contains
 131       */
 132     public boolean equals(Object obj) {
 133         if ((obj != null) && (obj instanceof Attribute)) {
 134             Attribute target = (Attribute)obj;
 135 
 136             // Check order first
 137             if (isOrdered() != target.isOrdered()) {
 138                 return false;
 139             }
 140             int len;
 141             if (attrID.equals(target.getID()) &&
 142                 (len=size()) == target.size()) {
 143                 try {
 144                     if (isOrdered()) {
 145                         // Go through both list of values
 146                         for (int i = 0; i < len; i++) {
 147                             if (!valueEquals(get(i), target.get(i))) {
 148                                 return false;
 149                             }
 150                         }
 151                     } else {
 152                         // order is not relevant; check for existence
 153                         Enumeration<?> theirs = target.getAll();
 154                         while (theirs.hasMoreElements()) {
 155                             if (find(theirs.nextElement()) < 0)
 156                                 return false;
 157                         }
 158                     }
 159                 } catch (NamingException e) {
 160                     return false;
 161                 }
 162                 return true;
 163             }
 164         }
 165         return false;
 166     }
 167 
 168     /**
 169       * Calculates the hash code of this attribute.
 170       *<p>
 171       * The hash code is computed by adding the hash code of
 172       * the attribute's id and that of all of its values except for
 173       * values that are arrays.
 174       * For an array, the hash code of each element of the array is summed.
 175       * If a subclass overrides {@code hashCode()}, it should override
 176       * {@code equals()}
 177       * as well so that two attributes that are equal have the same hash code.
 178       *
 179       * @return an int representing the hash code of this attribute.
 180       * @see #equals
 181       */
 182     public int hashCode() {
 183         int hash = attrID.hashCode();
 184         int num = values.size();
 185         Object val;
 186         for (int i = 0; i < num; i ++) {
 187             val = values.elementAt(i);
 188             if (val != null) {
 189                 if (val.getClass().isArray()) {
 190                     Object it;
 191                     int len = Array.getLength(val);
 192                     for (int j = 0 ; j < len ; j++) {
 193                         it = Array.get(val, j);
 194                         if (it != null) {
 195                             hash += it.hashCode();
 196                         }
 197                     }
 198                 } else {
 199                     hash += val.hashCode();
 200                 }
 201             }
 202         }
 203         return hash;
 204     }
 205 
 206     /**
 207       * Generates the string representation of this attribute.
 208       * The string consists of the attribute's id and its values.
 209       * This string is meant for debugging and not meant to be
 210       * interpreted programmatically.
 211       * @return The non-null string representation of this attribute.
 212       */
 213     public String toString() {
 214         StringBuilder answer = new StringBuilder(attrID + ": ");
 215         if (values.size() == 0) {
 216             answer.append("No values");
 217         } else {
 218             boolean start = true;
 219             for (Enumeration<Object> e = values.elements(); e.hasMoreElements(); ) {
 220                 if (!start)
 221                     answer.append(", ");
 222                 answer.append(e.nextElement());
 223                 start = false;
 224             }
 225         }
 226         return answer.toString();
 227     }
 228 
 229     /**
 230       * Constructs a new instance of an unordered attribute with no value.
 231       *
 232       * @param id The attribute's id. It cannot be null.
 233       */
 234     public BasicAttribute(String id) {
 235         this(id, false);
 236     }
 237 
 238     /**
 239       * Constructs a new instance of an unordered attribute with a single value.
 240       *
 241       * @param id The attribute's id. It cannot be null.
 242       * @param value The attribute's value. If null, a null
 243       *        value is added to the attribute.
 244       */
 245     public BasicAttribute(String id, Object value) {
 246         this(id, value, false);
 247     }
 248 
 249     /**
 250       * Constructs a new instance of a possibly ordered attribute with no value.
 251       *
 252       * @param id The attribute's id. It cannot be null.
 253       * @param ordered true means the attribute's values will be ordered;
 254       * false otherwise.
 255       */
 256     public BasicAttribute(String id, boolean ordered) {
 257         attrID = id;
 258         values = new Vector<>();
 259         this.ordered = ordered;
 260     }
 261 
 262     /**
 263       * Constructs a new instance of a possibly ordered attribute with a
 264       * single value.
 265       *
 266       * @param id The attribute's id. It cannot be null.
 267       * @param value The attribute's value. If null, a null
 268       *        value is added to the attribute.
 269       * @param ordered true means the attribute's values will be ordered;
 270       * false otherwise.
 271       */
 272     public BasicAttribute(String id, Object value, boolean ordered) {
 273         this(id, ordered);
 274         values.addElement(value);
 275     }
 276 
 277     /**
 278       * Retrieves an enumeration of this attribute's values.
 279       *<p>
 280       * By default, the values returned are those passed to the
 281       * constructor and/or manipulated using the add/replace/remove methods.
 282       * A subclass may override this to retrieve the values dynamically
 283       * from the directory.
 284       */
 285     public NamingEnumeration<?> getAll() throws NamingException {
 286       return new ValuesEnumImpl();
 287     }
 288 
 289     /**
 290       * Retrieves one of this attribute's values.
 291       *<p>
 292       * By default, the value returned is one of those passed to the
 293       * constructor and/or manipulated using the add/replace/remove methods.
 294       * A subclass may override this to retrieve the value dynamically
 295       * from the directory.
 296       */
 297     public Object get() throws NamingException {
 298         if (values.size() == 0) {
 299             throw new
 300         NoSuchElementException("Attribute " + getID() + " has no value");
 301         } else {
 302             return values.elementAt(0);
 303         }
 304     }
 305 
 306     public int size() {
 307       return values.size();
 308     }
 309 
 310     public String getID() {
 311         return attrID;
 312     }
 313 
 314     /**
 315       * Determines whether a value is in this attribute.
 316       *<p>
 317       * By default,
 318       * {@code Object.equals()} is used when comparing {@code attrVal}
 319       * with this attribute's values except when {@code attrVal} is an array.
 320       * For an array, each element of the array is checked using
 321       * {@code Object.equals()}.
 322       * A subclass may use schema information to determine equality.
 323       */
 324     public boolean contains(Object attrVal) {
 325         return (find(attrVal) >= 0);
 326     }
 327 
 328     // For finding first element that has a null in JDK1.1 Vector.
 329     // In the Java 2 platform, can just replace this with Vector.indexOf(target);
 330     private int find(Object target) {
 331         Class<?> cl;
 332         if (target == null) {
 333             int ct = values.size();
 334             for (int i = 0 ; i < ct ; i++) {
 335                 if (values.elementAt(i) == null)
 336                     return i;
 337             }
 338         } else if ((cl=target.getClass()).isArray()) {
 339             int ct = values.size();
 340             Object it;
 341             for (int i = 0 ; i < ct ; i++) {
 342                 it = values.elementAt(i);
 343                 if (it != null && cl == it.getClass()
 344                     && arrayEquals(target, it))
 345                     return i;
 346             }
 347         } else {
 348             return values.indexOf(target, 0);
 349         }
 350         return -1;  // not found
 351     }
 352 
 353     /**
 354      * Determines whether two attribute values are equal.
 355      * Use arrayEquals for arrays and {@code Object.equals()} otherwise.
 356      */
 357     private static boolean valueEquals(Object obj1, Object obj2) {
 358         if (obj1 == obj2) {
 359             return true; // object references are equal
 360         }
 361         if (obj1 == null) {
 362             return false; // obj2 was not false
 363         }
 364         if (obj1.getClass().isArray() &&
 365             obj2.getClass().isArray()) {
 366             return arrayEquals(obj1, obj2);
 367         }
 368         return (obj1.equals(obj2));
 369     }
 370 
 371     /**
 372      * Determines whether two arrays are equal by comparing each of their
 373      * elements using {@code Object.equals()}.
 374      */
 375     private static boolean arrayEquals(Object a1, Object a2) {
 376         int len;
 377         if ((len = Array.getLength(a1)) != Array.getLength(a2))
 378             return false;
 379 
 380         for (int j = 0; j < len; j++) {
 381             Object i1 = Array.get(a1, j);
 382             Object i2 = Array.get(a2, j);
 383             if (i1 == null || i2 == null) {
 384                 if (i1 != i2)
 385                     return false;
 386             } else if (!i1.equals(i2)) {
 387                 return false;
 388             }
 389         }
 390         return true;
 391     }
 392 
 393     /**
 394       * Adds a new value to this attribute.
 395       *<p>
 396       * By default, {@code Object.equals()} is used when comparing {@code attrVal}
 397       * with this attribute's values except when {@code attrVal} is an array.
 398       * For an array, each element of the array is checked using
 399       * {@code Object.equals()}.
 400       * A subclass may use schema information to determine equality.
 401       */
 402     public boolean add(Object attrVal) {
 403         if (isOrdered() || (find(attrVal) < 0)) {
 404             values.addElement(attrVal);
 405             return true;
 406         } else {
 407             return false;
 408         }
 409     }
 410 
 411     /**
 412       * Removes a specified value from this attribute.
 413       *<p>
 414       * By default, {@code Object.equals()} is used when comparing {@code attrVal}
 415       * with this attribute's values except when {@code attrVal} is an array.
 416       * For an array, each element of the array is checked using
 417       * {@code Object.equals()}.
 418       * A subclass may use schema information to determine equality.
 419       */
 420     public boolean remove(Object attrval) {
 421         // For the Java 2 platform, can just use "return removeElement(attrval);"
 422         // Need to do the following to handle null case
 423 
 424         int i = find(attrval);
 425         if (i >= 0) {
 426             values.removeElementAt(i);
 427             return true;
 428         }
 429         return false;
 430     }
 431 
 432     public void clear() {
 433         values.setSize(0);
 434     }
 435 
 436 //  ---- ordering methods
 437 
 438     public boolean isOrdered() {
 439         return ordered;
 440     }
 441 
 442     public Object get(int ix) throws NamingException {
 443         return values.elementAt(ix);
 444     }
 445 
 446     public Object remove(int ix) {
 447         Object answer = values.elementAt(ix);
 448         values.removeElementAt(ix);
 449         return answer;
 450     }
 451 
 452     public void add(int ix, Object attrVal) {
 453         if (!isOrdered() && contains(attrVal)) {
 454             throw new IllegalStateException(
 455                 "Cannot add duplicate to unordered attribute");
 456         }
 457         values.insertElementAt(attrVal, ix);
 458     }
 459 
 460     public Object set(int ix, Object attrVal) {
 461         if (!isOrdered() && contains(attrVal)) {
 462             throw new IllegalStateException(
 463                 "Cannot add duplicate to unordered attribute");
 464         }
 465 
 466         Object answer = values.elementAt(ix);
 467         values.setElementAt(attrVal, ix);
 468         return answer;
 469     }
 470 
 471 // ----------------- Schema methods
 472 
 473     /**
 474       * Retrieves the syntax definition associated with this attribute.
 475       *<p>
 476       * This method by default throws OperationNotSupportedException. A subclass
 477       * should override this method if it supports schema.
 478       */
 479     public DirContext getAttributeSyntaxDefinition() throws NamingException {
 480             throw new OperationNotSupportedException("attribute syntax");
 481     }
 482 
 483     /**
 484       * Retrieves this attribute's schema definition.
 485       *<p>
 486       * This method by default throws OperationNotSupportedException. A subclass
 487       * should override this method if it supports schema.
 488       */
 489     public DirContext getAttributeDefinition() throws NamingException {
 490         throw new OperationNotSupportedException("attribute definition");
 491     }
 492 
 493 
 494 //  ---- serialization methods
 495 
 496     /**
 497      * @serialData Default field (the attribute ID - a {@code String}),
 498      * followed by the number of values (an {@code int}), and the
 499      * individual values.
 500      *
 501      * @param s the {@code ObjectOutputStream} to write to
 502      * @throws java.io.IOException if an I/O error occurs.
 503      */
 504     @java.io.Serial
 505     private void writeObject(java.io.ObjectOutputStream s)
 506             throws java.io.IOException {
 507         // Overridden to avoid exposing implementation details
 508         s.defaultWriteObject(); // write out the attrID
 509         s.writeInt(values.size());
 510         for (int i = 0; i < values.size(); i++) {
 511             s.writeObject(values.elementAt(i));
 512         }
 513     }
 514 
 515     /**
 516      * Initializes the {@code BasicAttribute} from deserialized data.
 517      *
 518      * See {@code writeObject} for a description of the serial form.
 519      *
 520      * @param s the {@code ObjectInputStream} to read from
 521      * @throws java.io.IOException if an I/O error occurs.
 522      * @throws ClassNotFoundException if the class of a serialized object
 523      *         could not be found.
 524      */
 525     @java.io.Serial
 526     private void readObject(java.io.ObjectInputStream s)
 527             throws java.io.IOException, ClassNotFoundException {
 528         // Overridden to avoid exposing implementation details.
 529         s.defaultReadObject();  // read in the attrID
 530         int n = s.readInt();    // number of values
 531         values = new Vector<>(Math.min(1024, n));
 532         while (--n >= 0) {
 533             values.addElement(s.readObject());
 534         }
 535     }
 536 
 537 
 538     class ValuesEnumImpl implements NamingEnumeration<Object> {
 539         Enumeration<Object> list;
 540 
 541         ValuesEnumImpl() {
 542             list = values.elements();
 543         }
 544 
 545         public boolean hasMoreElements() {
 546             return list.hasMoreElements();
 547         }
 548 
 549         public Object nextElement() {
 550             return(list.nextElement());
 551         }
 552 
 553         public Object next() throws NamingException {
 554             return list.nextElement();
 555         }
 556 
 557         public boolean hasMore() throws NamingException {
 558             return list.hasMoreElements();
 559         }
 560 
 561         public void close() throws NamingException {
 562             list = null;
 563         }
 564     }
 565 
 566     /**
 567      * Use serialVersionUID from JNDI 1.1.1 for interoperability.
 568      */
 569     private static final long serialVersionUID = 6743528196119291326L;
 570 }