001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package javax.naming.directory;
019:
020: import java.io.IOException;
021: import java.io.ObjectInputStream;
022: import java.io.ObjectOutputStream;
023: import java.lang.reflect.Array;
024: import java.util.Enumeration;
025: import java.util.NoSuchElementException;
026: import java.util.Vector;
027:
028: import javax.naming.NamingEnumeration;
029: import javax.naming.NamingException;
030: import javax.naming.OperationNotSupportedException;
031:
032: import org.apache.harmony.jndi.internal.nls.Messages;
033:
034: /**
035: * A simple attribute of a directory entry.
036: * <p>
037: * A basic attribute does not have any schema associated with it, and attempts
038: * to get the schema result in an <code>OperationNotSupportedException</code>
039: * being thrown.
040: * </p>
041: * <p>
042: * The definition of <code>equals</code> for an attribute is simply <code>
043: * Object.equals</code>
044: * on the value, except for values that are collections where the definition of
045: * <code>equals</code> is an equivalence test (i.e. the collection contains
046: * the same number of elements, and each has an equal element in the other
047: * collection). For an array, <code>Object.equals</code> is used on each array
048: * element.
049: * </p>
050: * <p>
051: * Note that updates to a basic attribute do not update the directory itself --
052: * updates to a directory are only possible through the {@link DirContext}
053: * interface. <code>BasicAttribute</code> does not get its values dynamically
054: * from the directory. It uses the values passed to the constructor or add and
055: * remove methods.
056: * </p>
057: *
058: * @see Attribute
059: */
060: public class BasicAttribute implements Attribute {
061:
062: /*
063: * This constant is used during deserialization to check the version which
064: * created the serialized object.
065: */
066: static final long serialVersionUID = 0x5d95d32a668565beL;
067:
068: /**
069: * The attribute identifier. It is initialized by the public constructors
070: * and is required to be not null.
071: *
072: * @serial
073: */
074: protected String attrID;
075:
076: /**
077: * Flag showing whether the values of the attribute are ordered.
078: *
079: * @serial
080: */
081: protected boolean ordered;
082:
083: /**
084: * <code>Vector</code> containing the attribute's values. This is
085: * initialized by the public constructor and is required to be not null.
086: */
087: protected transient Vector<Object> values = new Vector<Object>();
088:
089: /**
090: * Constructs an unordered <code>BasicAttribute</code> instance with the
091: * supplied identifier and no values.
092: *
093: * @param id
094: * the attribute ID
095: */
096: public BasicAttribute(String id) {
097: this (id, false);
098: }
099:
100: /**
101: * Constructs a <code>BasicAttribute</code> instance with the supplied
102: * identifier and no values. The supplied flag controls whether the values
103: * will be ordered or not.
104: *
105: * @param id
106: * the attribute ID
107: * @param flag
108: * Indicates whether the values are ordered or not.
109: */
110: public BasicAttribute(String id, boolean flag) {
111: attrID = id;
112: ordered = flag;
113: }
114:
115: /**
116: * Constructs an unordered <code>BasicAttribute</code> instance with the
117: * supplied identifier and one value.
118: *
119: * @param id
120: * the attribute ID
121: * @param val
122: * the first attribute value
123: */
124: public BasicAttribute(String id, Object val) {
125: this (id, val, false);
126: }
127:
128: /**
129: * Constructs a <code>BasicAttribute</code> instance with the supplied
130: * identifier and one value. The supplied flag controls whether the values
131: * will be ordered or not.
132: *
133: * @param id
134: * the attribute ID
135: * @param val
136: * the first attribute value
137: * @param flag
138: * Indicates whether the values are ordered or not.
139: */
140: public BasicAttribute(String id, Object val, boolean flag) {
141: this (id, flag);
142: values.add(val);
143: }
144:
145: /*
146: * Determine whether two values belonging to the two array classes
147: * respectively are possible to be equal.
148: */
149: private boolean compareValueClasses(Class<? extends Object> c1,
150: Class<? extends Object> c2) {
151: if ((c1.getName().startsWith("[L") || c1.getName().startsWith("[[")) && //$NON-NLS-1$ //$NON-NLS-2$
152: (c2.getName().startsWith("[L") || c2.getName().startsWith("[["))) { //$NON-NLS-1$ //$NON-NLS-2$
153: /*
154: * If both Class are array of Object or array of array, the compare
155: * result is true, even if their class name may not be the same.
156: */
157: return true;
158: } else if (c1.getName().equals(c2.getName())) {
159: /*
160: * Otherwise, at least one of them must be array of basic types. If
161: * both Class have the same Class name, the compare result is true.
162: */
163: return true;
164: } else {
165: /*
166: * Otherwise, the compare result is false
167: */
168: return false;
169: }
170: }
171:
172: /*
173: * Determine whether the two values are equal with each other, considering
174: * the possibility that they might be both arrays so that each element of
175: * them has to be compared.
176: */
177: private boolean compareValues(Object obj1, Object obj2) {
178: if (null == obj1 && null == obj2) {
179: // If both are null, they are considered equal.
180: return true;
181: } else if (null != obj1 && null != obj2) {
182: if (obj1.getClass().isArray() && obj2.getClass().isArray()) {
183: /*
184: * If both are array, compare each element if it is possible
185: * that they might be equal.
186: */
187: if (compareValueClasses(obj1.getClass(), obj2
188: .getClass())) {
189: int i = Array.getLength(obj1);
190: Object val1;
191: Object val2;
192:
193: // Compare each element of the two arrays
194: if (Array.getLength(obj2) == i) {
195: // Do the compare only if their lengths are equal
196: for (i--; i >= 0; i--) {
197: val1 = Array.get(obj1, i);
198: val2 = Array.get(obj2, i);
199: if (null == val1 ? null != val2 : !val1
200: .equals(val2)) {
201: /*
202: * If any of their elements at the same position
203: * are not equal,they are not equal.
204: */
205: return false;
206: }
207: }
208: // If all elements are equal, they are equal
209: return true;
210: }
211: // Not equal if different length
212: return false;
213: }
214: // Not equal if this can be inferred from their class names
215: return false;
216: }
217: // If not both of them are array, do a normal "equals"
218: return obj1.equals(obj2);
219: } else {
220: // Not equal if only one of them is null
221: return false;
222: }
223: }
224:
225: /*
226: * Get the hash code of an attribute value, which might be an array whose
227: * hash code is the sum of all its element. Base types are converted into
228: * corresponding wrapper class objects.
229: */
230: private int hashCodeOfValue(Object obj) {
231: int hashcode = 0;
232:
233: if (null != obj) {
234: // If the object is an array, sum up the hashcode of all elements.
235: if (obj.getClass().isArray()) {
236: Object element = null;
237: // Sum up the hashcode of all elements
238: for (int i = Array.getLength(obj) - 1; i >= 0; i--) {
239: element = Array.get(obj, i);
240: if (null != element) {
241: hashcode += element.hashCode();
242: }
243: }
244: } else {
245: // Otherwise, simply get the hashcode of the given object.
246: hashcode = obj.hashCode();
247: }
248: }
249:
250: return hashcode;
251: }
252:
253: public void add(int index, Object val) {
254: if (ordered) {
255: values.add(index, val);
256: } else {
257: if (contains(val)) {
258: // jndi.16=Value already exists.
259: throw new IllegalStateException(Messages
260: .getString("jndi.16")); //$NON-NLS-1$
261: }
262: values.add(index, val);
263: }
264: }
265:
266: public boolean add(Object val) {
267: if (ordered) {
268: return values.add(val); // always true
269: }
270: if (contains(val)) {
271: return false;
272: }
273: return values.add(val); // always true
274: }
275:
276: public void clear() {
277: values.clear();
278: }
279:
280: @SuppressWarnings("unchecked")
281: @Override
282: public Object clone() {
283: try {
284: BasicAttribute attr = (BasicAttribute) super .clone();
285: attr.values = (Vector<Object>) this .values.clone();
286: return attr;
287: } catch (CloneNotSupportedException e) {
288: // jndi.17=Failed to clone object of BasicAttribute class.
289: throw new AssertionError(Messages.getString("jndi.17")); //$NON-NLS-1$
290: }
291: }
292:
293: public boolean contains(Object val) {
294: Enumeration<Object> e = this .values.elements();
295:
296: while (e.hasMoreElements()) {
297: if (compareValues(e.nextElement(), val)) {
298: return true;
299: }
300: }
301: return false;
302: }
303:
304: public Object get() throws NamingException {
305: if (0 == values.size()) {
306: // jndi.18=No values available.
307: throw new NoSuchElementException(Messages
308: .getString("jndi.18")); //$NON-NLS-1$
309: }
310: return values.get(0);
311: }
312:
313: public Object get(int index) throws NamingException {
314: return values.get(index);
315: }
316:
317: public NamingEnumeration<?> getAll() throws NamingException {
318: return new BasicNamingEnumeration<Object>(values.elements());
319: }
320:
321: public DirContext getAttributeDefinition() throws NamingException {
322: // jndi.19=BasicAttribute does not support this operation.
323: throw new OperationNotSupportedException(Messages
324: .getString("jndi.19")); //$NON-NLS-1$
325: }
326:
327: public DirContext getAttributeSyntaxDefinition()
328: throws NamingException {
329: // jndi.19=BasicAttribute does not support this operation.
330: throw new OperationNotSupportedException(Messages
331: .getString("jndi.19")); //$NON-NLS-1$
332: }
333:
334: public String getID() {
335: return attrID;
336: }
337:
338: public boolean isOrdered() {
339: return ordered;
340: }
341:
342: public Object remove(int index) {
343: return values.remove(index);
344: }
345:
346: public boolean remove(Object val) {
347: int total = this .values.size();
348:
349: for (int i = 0; i < total; i++) {
350: if (compareValues(this .values.get(i), val)) {
351: this .values.remove(i);
352: return true;
353: }
354: }
355: return false;
356: }
357:
358: public Object set(int index, Object val) {
359: if (!ordered && contains(val)) {
360: // jndi.16=Value already exists.
361: throw new IllegalStateException(Messages
362: .getString("jndi.16")); //$NON-NLS-1$
363: }
364: return values.set(index, val);
365: }
366:
367: public int size() {
368: return values.size();
369: }
370:
371: /*
372: * Serialization of the BasicAttribute class is as follows: attribute
373: * identifier (String) ordered flag (boolean) number of values (int) list of
374: * value objects
375: */
376: private void readObject(ObjectInputStream ois) throws IOException,
377: ClassNotFoundException {
378: int size;
379:
380: ois.defaultReadObject();
381: size = ois.readInt();
382: this .values = new Vector<Object>();
383: for (int i = 0; i < size; i++) {
384: this .values.add(ois.readObject());
385: }
386: }
387:
388: /*
389: * Serialization of the BasicAttribute class is as follows: attribute
390: * identifier (String) ordered flag (boolean) number of values (int) list of
391: * value objects
392: */
393: private void writeObject(ObjectOutputStream oos) throws IOException {
394: oos.defaultWriteObject();
395: oos.writeInt(this .values.size());
396: for (Object object : this .values) {
397: oos.writeObject(object);
398: }
399: }
400:
401: /**
402: * Returns true if this <code>BasicAttribute</code> instance is equal to
403: * the supplied object <code>obj</code>. Two attributes are considered
404: * equal if they have equal identifiers, schemas and values. BasicAttribute
405: * uses no schema.
406: * <p>
407: * <code>Object.equals</code> is used to test equality of identifiers and
408: * values. For array values <code>Object.equals</code> is called on every
409: * array element.
410: * </p>
411: *
412: * @param obj
413: * the object to be compared with
414: * @return true if this object is equal to <code>obj</code>, otherwise
415: * false
416: */
417: @Override
418: public boolean equals(Object obj) {
419: if (obj instanceof BasicAttribute) {
420: BasicAttribute a = (BasicAttribute) obj;
421:
422: if (!this .attrID.equals(a.attrID)) {
423: // Not equal if different ID
424: return false;
425: } else if (this .ordered != a.ordered) {
426: // Not equal if different order definition
427: return false;
428: } else if (this .values.size() != a.values.size()) {
429: // Not equal if different numbers of values
430: return false;
431: } else if (this .ordered) {
432: // Otherwise, if both ordered, compare each value
433: Enumeration<?> e1 = this .values.elements();
434: Enumeration<?> e2 = a.values.elements();
435:
436: while (e1.hasMoreElements()) {
437: if (!compareValues(e1.nextElement(), e2
438: .nextElement())) {
439: // Not equal if one of the values are not equal
440: return false;
441: }
442: }
443: // Equal only if all the values are equal
444: return true;
445: } else {
446: /*
447: * Otherwise (i.e., both unordered), see whether containing the
448: * equal set of values.
449: */
450: Enumeration<Object> e = this .values.elements();
451:
452: while (e.hasMoreElements()) {
453: if (!a.contains(e.nextElement())) {
454: return false;
455: }
456: }
457: return true;
458: }
459: }
460: // Not equal if not instance of BasicAttribute
461: return false;
462: }
463:
464: /**
465: * Returns the hashcode for this <code>BasicAttribute</code> instance. The
466: * result is calculated by summing the hashcodes for the identifier and each
467: * of the values, except for array values, where the hashcodes for each
468: * array element are summed.
469: *
470: * @return the hashcode of this <code>BasicAttribute</code> instance
471: */
472: @Override
473: public int hashCode() {
474: Object o;
475: int i = attrID.hashCode();
476: Enumeration<Object> e = this .values.elements();
477:
478: while (e.hasMoreElements()) {
479: o = e.nextElement();
480: if (null != o) {
481: i += hashCodeOfValue(o);
482: }
483: }
484:
485: return i;
486: }
487:
488: /**
489: * Returns the string representation of this <code>BasicAttribute</code>
490: * instance. The result contains the ID and the string representation of
491: * each value.
492: *
493: * @return the string representation of this object
494: */
495: @Override
496: public String toString() {
497: Enumeration<Object> e = this .values.elements();
498: String s = "Attribute ID: " + this .attrID; //$NON-NLS-1$
499: s += "\nAttribute values: "; //$NON-NLS-1$
500:
501: if (!e.hasMoreElements()) {
502: s += "This Attribute does not have any values."; //$NON-NLS-1$
503: } else {
504: s += e.nextElement();
505: while (e.hasMoreElements()) {
506: s += "," + e.nextElement(); //$NON-NLS-1$
507: }
508: }
509: return s + "\n"; //$NON-NLS-1$
510: }
511:
512: }
|