/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package javax.naming.directory;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.Vector;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.OperationNotSupportedException;
import org.apache.harmony.jndi.internal.nls.Messages;
/**
* A simple attribute of a directory entry.
* <p>
* A basic attribute does not have any schema associated with it, and attempts
* to get the schema result in an <code>OperationNotSupportedException</code>
* being thrown.
* </p>
* <p>
* The definition of <code>equals</code> for an attribute is simply <code>
* Object.equals</code>
* on the value, except for values that are collections where the definition of
* <code>equals</code> is an equivalence test (i.e. the collection contains
* the same number of elements, and each has an equal element in the other
* collection). For an array, <code>Object.equals</code> is used on each array
* element.
* </p>
* <p>
* Note that updates to a basic attribute do not update the directory itself --
* updates to a directory are only possible through the {@link DirContext}
* interface. <code>BasicAttribute</code> does not get its values dynamically
* from the directory. It uses the values passed to the constructor or add and
* remove methods.
* </p>
*
* @see Attribute
*/
public class BasicAttribute implements Attribute {
/*
* This constant is used during deserialization to check the version which
* created the serialized object.
*/
private static final long serialVersionUID = 0x5d95d32a668565beL;
/**
* The attribute identifier. It is initialized by the public constructors
* and is required to be not null.
*
* @serial
*/
protected String attrID;
/**
* Flag showing whether the values of the attribute are ordered.
*
* @serial
*/
protected boolean ordered;
/**
* <code>Vector</code> containing the attribute's values. This is
* initialized by the public constructor and is required to be not null.
*/
protected transient Vector<Object> values = new Vector<Object>();
/**
* Constructs an unordered <code>BasicAttribute</code> instance with the
* supplied identifier and no values.
*
* @param id
* the attribute ID
*/
public BasicAttribute(String id) {
this(id, false);
}
/**
* Constructs a <code>BasicAttribute</code> instance with the supplied
* identifier and no values. The supplied flag controls whether the values
* will be ordered or not.
*
* @param id
* the attribute ID
* @param flag
* Indicates whether the values are ordered or not.
*/
public BasicAttribute(String id, boolean flag) {
attrID = id;
ordered = flag;
}
/**
* Constructs an unordered <code>BasicAttribute</code> instance with the
* supplied identifier and one value.
*
* @param id
* the attribute ID
* @param val
* the first attribute value
*/
public BasicAttribute(String id, Object val) {
this(id, val, false);
}
/**
* Constructs a <code>BasicAttribute</code> instance with the supplied
* identifier and one value. The supplied flag controls whether the values
* will be ordered or not.
*
* @param id
* the attribute ID
* @param val
* the first attribute value
* @param flag
* Indicates whether the values are ordered or not.
*/
public BasicAttribute(String id, Object val, boolean flag) {
this(id, flag);
values.add(val);
}
/*
* Determine whether two values belonging to the two array classes
* respectively are possible to be equal.
*/
private boolean compareValueClasses(Class<? extends Object> c1,
Class<? extends Object> c2) {
if ((c1.getName().startsWith("[L") || c1.getName().startsWith("[[")) && //$NON-NLS-1$ //$NON-NLS-2$
(c2.getName().startsWith("[L") || c2.getName().startsWith("[["))) { //$NON-NLS-1$ //$NON-NLS-2$
/*
* If both Class are array of Object or array of array, the compare
* result is true, even if their class name may not be the same.
*/
return true;
} else if (c1.getName().equals(c2.getName())) {
/*
* Otherwise, at least one of them must be array of basic types. If
* both Class have the same Class name, the compare result is true.
*/
return true;
} else {
/*
* Otherwise, the compare result is false
*/
return false;
}
}
/*
* Determine whether the two values are equal with each other, considering
* the possibility that they might be both arrays so that each element of
* them has to be compared.
*/
private boolean compareValues(Object obj1, Object obj2) {
if (null == obj1 && null == obj2) {
// If both are null, they are considered equal.
return true;
} else if (null != obj1 && null != obj2) {
if (obj1.getClass().isArray() && obj2.getClass().isArray()) {
/*
* If both are array, compare each element if it is possible
* that they might be equal.
*/
if (compareValueClasses(obj1.getClass(), obj2.getClass())) {
int i = Array.getLength(obj1);
Object val1;
Object val2;
// Compare each element of the two arrays
if (Array.getLength(obj2) == i) {
// Do the compare only if their lengths are equal
for (i--; i >= 0; i--) {
val1 = Array.get(obj1, i);
val2 = Array.get(obj2, i);
if (null == val1 ? null != val2 : !val1
.equals(val2)) {
/*
* If any of their elements at the same position
* are not equal,they are not equal.
*/
return false;
}
}
// If all elements are equal, they are equal
return true;
}
// Not equal if different length
return false;
}
// Not equal if this can be inferred from their class names
return false;
}
// If not both of them are array, do a normal "equals"
return obj1.equals(obj2);
} else {
// Not equal if only one of them is null
return false;
}
}
/*
* Get the hash code of an attribute value, which might be an array whose
* hash code is the sum of all its element. Base types are converted into
* corresponding wrapper class objects.
*/
private int hashCodeOfValue(Object obj) {
int hashcode = 0;
if (null != obj) {
// If the object is an array, sum up the hashcode of all elements.
if (obj.getClass().isArray()) {
Object element = null;
// Sum up the hashcode of all elements
for (int i = Array.getLength(obj) - 1; i >= 0; i--) {
element = Array.get(obj, i);
if (null != element) {
hashcode += element.hashCode();
}
}
} else {
// Otherwise, simply get the hashcode of the given object.
hashcode = obj.hashCode();
}
}
return hashcode;
}
public void add(int index, Object val) {
if (ordered) {
values.add(index, val);
} else {
if (contains(val)) {
// jndi.16=Value already exists.
throw new IllegalStateException(Messages.getString("jndi.16")); //$NON-NLS-1$
}
values.add(index, val);
}
}
public boolean add(Object val) {
if (ordered) {
return values.add(val); // always true
}
if (contains(val)) {
return false;
}
return values.add(val); // always true
}
public void clear() {
values.clear();
}
@SuppressWarnings("unchecked")
@Override
public Object clone() {
try {
BasicAttribute attr = (BasicAttribute) super.clone();
attr.values = (Vector<Object>) this.values.clone();
return attr;
} catch (CloneNotSupportedException e) {
// jndi.17=Failed to clone object of BasicAttribute class.
throw new AssertionError(Messages.getString("jndi.17")); //$NON-NLS-1$
}
}
public boolean contains(Object val) {
Enumeration<Object> e = this.values.elements();
while (e.hasMoreElements()) {
if (compareValues(e.nextElement(), val)) {
return true;
}
}
return false;
}
public Object get() throws NamingException {
if (0 == values.size()) {
// jndi.18=No values available.
throw new NoSuchElementException(Messages.getString("jndi.18")); //$NON-NLS-1$
}
return values.get(0);
}
public Object get(int index) throws NamingException {
return values.get(index);
}
public NamingEnumeration<?> getAll() throws NamingException {
return new BasicNamingEnumeration<Object>(values.elements());
}
public DirContext getAttributeDefinition() throws NamingException {
// jndi.19=BasicAttribute does not support this operation.
throw new OperationNotSupportedException(Messages.getString("jndi.19")); //$NON-NLS-1$
}
public DirContext getAttributeSyntaxDefinition() throws NamingException {
// jndi.19=BasicAttribute does not support this operation.
throw new OperationNotSupportedException(Messages.getString("jndi.19")); //$NON-NLS-1$
}
public String getID() {
return attrID;
}
public boolean isOrdered() {
return ordered;
}
public Object remove(int index) {
return values.remove(index);
}
public boolean remove(Object val) {
int total = this.values.size();
for (int i = 0; i < total; i++) {
if (compareValues(this.values.get(i), val)) {
this.values.remove(i);
return true;
}
}
return false;
}
public Object set(int index, Object val) {
if (!ordered && contains(val)) {
// jndi.16=Value already exists.
throw new IllegalStateException(Messages.getString("jndi.16")); //$NON-NLS-1$
}
return values.set(index, val);
}
public int size() {
return values.size();
}
/*
* Serialization of the BasicAttribute class is as follows: attribute
* identifier (String) ordered flag (boolean) number of values (int) list of
* value objects
*/
private void readObject(ObjectInputStream ois) throws IOException,
ClassNotFoundException {
int size;
ois.defaultReadObject();
size = ois.readInt();
this.values = new Vector<Object>();
for (int i = 0; i < size; i++) {
this.values.add(ois.readObject());
}
}
/*
* Serialization of the BasicAttribute class is as follows: attribute
* identifier (String) ordered flag (boolean) number of values (int) list of
* value objects
*/
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
oos.writeInt(this.values.size());
for (Object object : this.values) {
oos.writeObject(object);
}
}
/**
* Returns true if this <code>BasicAttribute</code> instance is equal to
* the supplied object <code>obj</code>. Two attributes are considered
* equal if they have equal identifiers, schemas and values. BasicAttribute
* uses no schema.
* <p>
* <code>Object.equals</code> is used to test equality of identifiers and
* values. For array values <code>Object.equals</code> is called on every
* array element.
* </p>
*
* @param obj
* the object to be compared with
* @return true if this object is equal to <code>obj</code>, otherwise
* false
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof BasicAttribute) {
BasicAttribute a = (BasicAttribute) obj;
if (!this.attrID.equals(a.attrID)) {
// Not equal if different ID
return false;
} else if (this.ordered != a.ordered) {
// Not equal if different order definition
return false;
} else if (this.values.size() != a.values.size()) {
// Not equal if different numbers of values
return false;
} else if (this.ordered) {
// Otherwise, if both ordered, compare each value
Enumeration<?> e1 = this.values.elements();
Enumeration<?> e2 = a.values.elements();
while (e1.hasMoreElements()) {
if (!compareValues(e1.nextElement(), e2.nextElement())) {
// Not equal if one of the values are not equal
return false;
}
}
// Equal only if all the values are equal
return true;
} else {
/*
* Otherwise (i.e., both unordered), see whether containing the
* equal set of values.
*/
Enumeration<Object> e = this.values.elements();
while (e.hasMoreElements()) {
if (!a.contains(e.nextElement())) {
return false;
}
}
return true;
}
}
// Not equal if not instance of BasicAttribute
return false;
}
/**
* Returns the hashcode for this <code>BasicAttribute</code> instance. The
* result is calculated by summing the hashcodes for the identifier and each
* of the values, except for array values, where the hashcodes for each
* array element are summed.
*
* @return the hashcode of this <code>BasicAttribute</code> instance
*/
@Override
public int hashCode() {
Object o;
int i = attrID.hashCode();
Enumeration<Object> e = this.values.elements();
while (e.hasMoreElements()) {
o = e.nextElement();
if (null != o) {
i += hashCodeOfValue(o);
}
}
return i;
}
/**
* Returns the string representation of this <code>BasicAttribute</code>
* instance. The result contains the ID and the string representation of
* each value.
*
* @return the string representation of this object
*/
@Override
public String toString() {
Enumeration<Object> e = this.values.elements();
String s = "Attribute ID: " + this.attrID; //$NON-NLS-1$
s += "\nAttribute values: "; //$NON-NLS-1$
if (!e.hasMoreElements()) {
s += "This Attribute does not have any values."; //$NON-NLS-1$
} else {
s += e.nextElement();
while (e.hasMoreElements()) {
s += "," + e.nextElement(); //$NON-NLS-1$
}
}
return s + "\n"; //$NON-NLS-1$
}
}