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 }