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  
27  package javax.naming.directory;
28  
29  import java.util.Hashtable;
30  import java.util.Enumeration;
31  import java.util.Locale;
32  
33  import javax.naming.NamingException;
34  import 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  
71  public 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 <tt>BasicAttributes</tt> is equal to another
211      * <tt>Attributes</tt>
212      * Two <tt>Attributes</tt> are equal if they are both instances of
213      * <tt>Attributes</tt>,
214      * treat the case of attribute IDs the same way, and contain the
215      * same attributes. Each <tt>Attribute</tt> in this <tt>BasicAttributes</tt>
216      * is checked for equality using <tt>Object.equals()</tt>, which may have
217      * be overridden by implementations of <tt>Attribute</tt>).
218      * If a subclass overrides <tt>equals()</tt>,
219      * it should override <tt>hashCode()</tt>
220      * as well so that two <tt>Attributes</tt> 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 <tt>hashCode()</tt>,
263      * it should override <tt>equals()</tt>
264      * as well so that two <tt>Attributes</tt> 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 
313 class 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 
342 class 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 }