1/*
2 * Copyright (c) 1999, 2017, 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
26package javax.naming.directory;
27
28import java.util.Vector;
29import java.util.Enumeration;
30import java.util.NoSuchElementException;
31import java.lang.reflect.Array;
32
33import javax.naming.NamingException;
34import javax.naming.NamingEnumeration;
35import 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  */
72public 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     * 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<>(Math.min(1024, 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}
558