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 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 {@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<>(1 + (int) (Math.min(768, n) / .75f)) 306 : new Hashtable<>(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 }