View Javadoc
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 }