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