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
27package javax.naming.directory;
28
29import java.util.Hashtable;
30import java.util.Enumeration;
31import java.util.Locale;
32
33import javax.naming.NamingException;
34import javax.naming.NamingEnumeration;
35
36/**
37  * This class provides a basic implementation
38  * of the Attributes interface.
39  *<p>
40  * BasicAttributes is either case-sensitive or case-insensitive (case-ignore).
41  * This property is determined at the time the BasicAttributes constructor
42  * is called.
43  * In a case-insensitive BasicAttributes, the case of its attribute identifiers
44  * is ignored when searching for an attribute, or adding attributes.
45  * In a case-sensitive BasicAttributes, the case is significant.
46  *<p>
47  * When the BasicAttributes class needs to create an Attribute, it
48  * uses BasicAttribute. There is no other dependency on BasicAttribute.
49  *<p>
50  * Note that updates to BasicAttributes (such as adding or removing an attribute)
51  * does not affect the corresponding representation in the directory.
52  * Updates to the directory can only be effected
53  * using operations in the DirContext interface.
54  *<p>
55  * A BasicAttributes instance is not synchronized against concurrent
56  * multithreaded access. Multiple threads trying to access and modify
57  * a single BasicAttributes instance should lock the object.
58  *
59  * @author Rosanna Lee
60  * @author Scott Seligman
61  *
62  * @see DirContext#getAttributes
63  * @see DirContext#modifyAttributes
64  * @see DirContext#bind
65  * @see DirContext#rebind
66  * @see DirContext#createSubcontext
67  * @see DirContext#search
68  * @since 1.3
69  */
70
71public class BasicAttributes implements Attributes {
72    /**
73     * Indicates whether case of attribute ids is ignored.
74     * @serial
75     */
76    private boolean ignoreCase = false;
77
78    // The 'key' in attrs is stored in the 'right case'.
79    // If ignoreCase is true, key is aways lowercase.
80    // If ignoreCase is false, key is stored as supplied by put().
81    // %%% Not declared "private" due to bug 4064984.
82    transient Hashtable<String,Attribute> attrs = new Hashtable<>(11);
83
84    /**
85      * Constructs a new instance of Attributes.
86      * The character case of attribute identifiers
87      * is significant when subsequently retrieving or adding attributes.
88      */
89    public BasicAttributes() {
90    }
91
92    /**
93      * Constructs a new instance of Attributes.
94      * If <code>ignoreCase</code> is true, the character case of attribute
95      * identifiers is ignored; otherwise the case is significant.
96      * @param ignoreCase true means this attribute set will ignore
97      *                   the case of its attribute identifiers
98      *                   when retrieving or adding attributes;
99      *                   false means case is respected.
100      */
101    public BasicAttributes(boolean ignoreCase) {
102        this.ignoreCase = ignoreCase;
103    }
104
105    /**
106      * Constructs a new instance of Attributes with one attribute.
107      * The attribute specified by attrID and val are added to the newly
108      * created attribute.
109      * The character case of attribute identifiers
110      * is significant when subsequently retrieving or adding attributes.
111      * @param attrID   non-null The id of the attribute to add.
112      * @param val The value of the attribute to add. If null, a null
113      *        value is added to the attribute.
114      */
115    public BasicAttributes(String attrID, Object val) {
116        this();
117        this.put(new BasicAttribute(attrID, val));
118    }
119
120    /**
121      * Constructs a new instance of Attributes with one attribute.
122      * The attribute specified by attrID and val are added to the newly
123      * created attribute.
124      * If <code>ignoreCase</code> is true, the character case of attribute
125      * identifiers is ignored; otherwise the case is significant.
126      * @param attrID   non-null The id of the attribute to add.
127      *           If this attribute set ignores the character
128      *           case of its attribute ids, the case of attrID
129      *           is ignored.
130      * @param val The value of the attribute to add. If null, a null
131      *        value is added to the attribute.
132      * @param ignoreCase true means this attribute set will ignore
133      *                   the case of its attribute identifiers
134      *                   when retrieving or adding attributes;
135      *                   false means case is respected.
136      */
137    public BasicAttributes(String attrID, Object val, boolean ignoreCase) {
138        this(ignoreCase);
139        this.put(new BasicAttribute(attrID, val));
140    }
141
142    @SuppressWarnings("unchecked")
143    public Object clone() {
144        BasicAttributes attrset;
145        try {
146            attrset = (BasicAttributes)super.clone();
147        } catch (CloneNotSupportedException e) {
148            attrset = new BasicAttributes(ignoreCase);
149        }
150        attrset.attrs = (Hashtable<String,Attribute>)attrs.clone();
151        return attrset;
152    }
153
154    public boolean isCaseIgnored() {
155        return ignoreCase;
156    }
157
158    public int size() {
159        return attrs.size();
160    }
161
162    public Attribute get(String attrID) {
163        Attribute attr = attrs.get(
164                ignoreCase ? attrID.toLowerCase(Locale.ENGLISH) : attrID);
165        return (attr);
166    }
167
168    public NamingEnumeration<Attribute> getAll() {
169        return new AttrEnumImpl();
170    }
171
172    public NamingEnumeration<String> getIDs() {
173        return new IDEnumImpl();
174    }
175
176    public Attribute put(String attrID, Object val) {
177        return this.put(new BasicAttribute(attrID, val));
178    }
179
180    public Attribute put(Attribute attr) {
181        String id = attr.getID();
182        if (ignoreCase) {
183            id = id.toLowerCase(Locale.ENGLISH);
184        }
185        return attrs.put(id, attr);
186    }
187
188    public Attribute remove(String attrID) {
189        String id = (ignoreCase ? attrID.toLowerCase(Locale.ENGLISH) : attrID);
190        return attrs.remove(id);
191    }
192
193    /**
194     * Generates the string representation of this attribute set.
195     * The string consists of each attribute identifier and the contents
196     * of each attribute. The contents of this string is useful
197     * for debugging and is not meant to be interpreted programmatically.
198     *
199     * @return A non-null string listing the contents of this attribute set.
200     */
201    public String toString() {
202        if (attrs.size() == 0) {
203            return("No attributes");
204        } else {
205            return attrs.toString();
206        }
207    }
208
209    /**
210     * Determines whether this {@code BasicAttributes} is equal to another
211     * {@code Attributes}
212     * Two {@code Attributes} are equal if they are both instances of
213     * {@code Attributes},
214     * treat the case of attribute IDs the same way, and contain the
215     * same attributes. Each {@code Attribute} in this {@code BasicAttributes}
216     * is checked for equality using {@code Object.equals()}, which may have
217     * be overridden by implementations of {@code Attribute}).
218     * If a subclass overrides {@code equals()},
219     * it should override {@code hashCode()}
220     * as well so that two {@code Attributes} instances that are equal
221     * have the same hash code.
222     * @param obj the possibly null object to compare against.
223     *
224     * @return true If obj is equal to this BasicAttributes.
225     * @see #hashCode
226     */
227    public boolean equals(Object obj) {
228        if ((obj != null) && (obj instanceof Attributes)) {
229            Attributes target = (Attributes)obj;
230
231            // Check case first
232            if (ignoreCase != target.isCaseIgnored()) {
233                return false;
234            }
235
236            if (size() == target.size()) {
237                Attribute their, mine;
238                try {
239                    NamingEnumeration<?> theirs = target.getAll();
240                    while (theirs.hasMore()) {
241                        their = (Attribute)theirs.next();
242                        mine = get(their.getID());
243                        if (!their.equals(mine)) {
244                            return false;
245                        }
246                    }
247                } catch (NamingException e) {
248                    return false;
249                }
250                return true;
251            }
252        }
253        return false;
254    }
255
256    /**
257     * Calculates the hash code of this BasicAttributes.
258     *<p>
259     * The hash code is computed by adding the hash code of
260     * the attributes of this object. If this BasicAttributes
261     * ignores case of its attribute IDs, one is added to the hash code.
262     * If a subclass overrides {@code hashCode()},
263     * it should override {@code equals()}
264     * as well so that two {@code Attributes} instances that are equal
265     * have the same hash code.
266     *
267     * @return an int representing the hash code of this BasicAttributes instance.
268     * @see #equals
269     */
270    public int hashCode() {
271        int hash = (ignoreCase ? 1 : 0);
272        try {
273            NamingEnumeration<?> all = getAll();
274            while (all.hasMore()) {
275                hash += all.next().hashCode();
276            }
277        } catch (NamingException e) {}
278        return hash;
279    }
280
281    /**
282     * Overridden to avoid exposing implementation details.
283     * @serialData Default field (ignoreCase flag -- a boolean), followed by
284     * the number of attributes in the set
285     * (an int), and then the individual Attribute objects.
286     */
287    private void writeObject(java.io.ObjectOutputStream s)
288            throws java.io.IOException {
289        s.defaultWriteObject(); // write out the ignoreCase flag
290        s.writeInt(attrs.size());
291        Enumeration<Attribute> attrEnum = attrs.elements();
292        while (attrEnum.hasMoreElements()) {
293            s.writeObject(attrEnum.nextElement());
294        }
295    }
296
297    /**
298     * Overridden to avoid exposing implementation details.
299     */
300    private void readObject(java.io.ObjectInputStream s)
301            throws java.io.IOException, ClassNotFoundException {
302        s.defaultReadObject();  // read in the ignoreCase flag
303        int n = s.readInt();    // number of attributes
304        attrs = (n >= 1)
305            ? new Hashtable<String,Attribute>(n * 2)
306            : new Hashtable<String,Attribute>(2); // can't have initial size of 0 (grrr...)
307        while (--n >= 0) {
308            put((Attribute)s.readObject());
309        }
310    }
311
312
313class AttrEnumImpl implements NamingEnumeration<Attribute> {
314
315    Enumeration<Attribute> elements;
316
317    public AttrEnumImpl() {
318        this.elements = attrs.elements();
319    }
320
321    public boolean hasMoreElements() {
322        return elements.hasMoreElements();
323    }
324
325    public Attribute nextElement() {
326        return elements.nextElement();
327    }
328
329    public boolean hasMore() throws NamingException {
330        return hasMoreElements();
331    }
332
333    public Attribute next() throws NamingException {
334        return nextElement();
335    }
336
337    public void close() throws NamingException {
338        elements = null;
339    }
340}
341
342class IDEnumImpl implements NamingEnumeration<String> {
343
344    Enumeration<Attribute> elements;
345
346    public IDEnumImpl() {
347        // Walking through the elements, rather than the keys, gives
348        // us attribute IDs that have not been converted to lowercase.
349        this.elements = attrs.elements();
350    }
351
352    public boolean hasMoreElements() {
353        return elements.hasMoreElements();
354    }
355
356    public String nextElement() {
357        Attribute attr = elements.nextElement();
358        return attr.getID();
359    }
360
361    public boolean hasMore() throws NamingException {
362        return hasMoreElements();
363    }
364
365    public String next() throws NamingException {
366        return nextElement();
367    }
368
369    public void close() throws NamingException {
370        elements = null;
371    }
372}
373
374    /**
375     * Use serialVersionUID from JNDI 1.1.1 for interoperability.
376     */
377    private static final long serialVersionUID = 4980164073184639448L;
378}
379