001 /*
002 * Copyright 1999-2004 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package javax.naming.directory;
027
028 import java.util.Vector;
029 import java.util.Enumeration;
030 import java.util.NoSuchElementException;
031 import java.lang.reflect.Array;
032
033 import javax.naming.NamingException;
034 import javax.naming.NamingEnumeration;
035 import javax.naming.OperationNotSupportedException;
036
037 /**
038 * This class provides a basic implementation of the <tt>Attribute</tt> interface.
039 *<p>
040 * This implementation does not support the schema methods
041 * <tt>getAttributeDefinition()</tt> and <tt>getAttributeSyntaxDefinition()</tt>.
042 * They simply throw <tt>OperationNotSupportedException</tt>.
043 * Subclasses of <tt>BasicAttribute</tt> should override these methods if they
044 * support them.
045 *<p>
046 * The <tt>BasicAttribute</tt> class by default uses <tt>Object.equals()</tt> to
047 * determine equality of attribute values when testing for equality or
048 * when searching for values, <em>except</em> when the value is an array.
049 * For an array, each element of the array is checked using <tt>Object.equals()</tt>.
050 * Subclasses of <tt>BasicAttribute</tt> can make use of schema information
051 * when doing similar equality checks by overriding methods
052 * in which such use of schema is meaningful.
053 * Similarly, the <tt>BasicAttribute</tt> class by default returns the values passed to its
054 * constructor and/or manipulated using the add/remove methods.
055 * Subclasses of <tt>BasicAttribute</tt> can override <tt>get()</tt> and <tt>getAll()</tt>
056 * to get the values dynamically from the directory (or implement
057 * the <tt>Attribute</tt> interface directly instead of subclassing <tt>BasicAttribute</tt>).
058 *<p>
059 * Note that updates to <tt>BasicAttribute</tt> (such as adding or removing a value)
060 * does not affect the corresponding representation of the attribute
061 * in the directory. Updates to the directory can only be effected
062 * using operations in the <tt>DirContext</tt> interface.
063 *<p>
064 * A <tt>BasicAttribute</tt> instance is not synchronized against concurrent
065 * multithreaded access. Multiple threads trying to access and modify a
066 * <tt>BasicAttribute</tt> should lock the object.
067 *
068 * @author Rosanna Lee
069 * @author Scott Seligman
070 * @version 1.20 07/05/05
071 * @since 1.3
072 */
073 public class BasicAttribute implements Attribute {
074 /**
075 * Holds the attribute's id. It is initialized by the public constructor and
076 * cannot be null unless methods in BasicAttribute that use attrID
077 * have been overridden.
078 * @serial
079 */
080 protected String attrID;
081
082 /**
083 * Holds the attribute's values. Initialized by public constructors.
084 * Cannot be null unless methods in BasicAttribute that use
085 * values have been overridden.
086 */
087 protected transient Vector<Object> values;
088
089 /**
090 * A flag for recording whether this attribute's values are ordered.
091 * @serial
092 */
093 protected boolean ordered = false;
094
095 public Object clone() {
096 BasicAttribute attr;
097 try {
098 attr = (BasicAttribute) super .clone();
099 } catch (CloneNotSupportedException e) {
100 attr = new BasicAttribute(attrID, ordered);
101 }
102 attr.values = (Vector) 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 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 NoSuchElementException("Attribute " + getID()
300 + " 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() && 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()
479 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 // ---- 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 class ValuesEnumImpl implements NamingEnumeration<Object> {
524 Enumeration list;
525
526 ValuesEnumImpl() {
527 list = values.elements();
528 }
529
530 public boolean hasMoreElements() {
531 return list.hasMoreElements();
532 }
533
534 public Object nextElement() {
535 return (list.nextElement());
536 }
537
538 public Object next() throws NamingException {
539 return list.nextElement();
540 }
541
542 public boolean hasMore() throws NamingException {
543 return list.hasMoreElements();
544 }
545
546 public void close() throws NamingException {
547 list = null;
548 }
549 }
550
551 /**
552 * Use serialVersionUID from JNDI 1.1.1 for interoperability.
553 */
554 private static final long serialVersionUID = 6743528196119291326L;
555 }
|