1 /* 2 * Copyright (c) 1999, 2011, 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 @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 <tt>Object.equals()</tt> 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 <tt>Object.equals()</tt>. 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 <tt>equals()</tt>, it should also override 124 * <tt>hashCode()</tt> 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 <tt>hashCode()</tt>, it should override 176 * <tt>equals()</tt> 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 StringBuffer answer = new StringBuffer(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 * <tt>Object.equals()</tt> is used when comparing <tt>attrVal</tt> 319 * with this attribute's values except when <tt>attrVal</tt> is an array. 320 * For an array, each element of the array is checked using 321 * <tt>Object.equals()</tt>. 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 <tt>Object.equals()</tt> 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 <tt>Object.equals()</tt>. 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, <tt>Object.equals()</tt> is used when comparing <tt>attrVal</tt> 397 * with this attribute's values except when <tt>attrVal</tt> is an array. 398 * For an array, each element of the array is checked using 399 * <tt>Object.equals()</tt>. 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, <tt>Object.equals()</tt> is used when comparing <tt>attrVal</tt> 415 * with this attribute's values except when <tt>attrVal</tt> is an array. 416 * For an array, each element of the array is checked using 417 * <tt>Object.equals()</tt>. 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 * Overridden to avoid exposing implementation details 498 * @serialData Default field (the attribute ID -- a String), 499 * followed by the number of values (an int), and the 500 * individual values. 501 */ 502 private void writeObject(java.io.ObjectOutputStream s) 503 throws java.io.IOException { 504 s.defaultWriteObject(); // write out the attrID 505 s.writeInt(values.size()); 506 for (int i = 0; i < values.size(); i++) { 507 s.writeObject(values.elementAt(i)); 508 } 509 } 510 511 /** 512 * Overridden to avoid exposing implementation details. 513 */ 514 private void readObject(java.io.ObjectInputStream s) 515 throws java.io.IOException, ClassNotFoundException { 516 s.defaultReadObject(); // read in the attrID 517 int n = s.readInt(); // number of values 518 values = new Vector<>(n); 519 while (--n >= 0) { 520 values.addElement(s.readObject()); 521 } 522 } 523 524 525 class ValuesEnumImpl implements NamingEnumeration<Object> { 526 Enumeration<Object> list; 527 528 ValuesEnumImpl() { 529 list = values.elements(); 530 } 531 532 public boolean hasMoreElements() { 533 return list.hasMoreElements(); 534 } 535 536 public Object nextElement() { 537 return(list.nextElement()); 538 } 539 540 public Object next() throws NamingException { 541 return list.nextElement(); 542 } 543 544 public boolean hasMore() throws NamingException { 545 return list.hasMoreElements(); 546 } 547 548 public void close() throws NamingException { 549 list = null; 550 } 551 } 552 553 /** 554 * Use serialVersionUID from JNDI 1.1.1 for interoperability. 555 */ 556 private static final long serialVersionUID = 6743528196119291326L; 557 }