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 }