/* * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javax.naming.directory; import java.util.Vector; import java.util.Enumeration; import java.util.NoSuchElementException; import java.lang.reflect.Array; import javax.naming.NamingException; import javax.naming.NamingEnumeration; import javax.naming.OperationNotSupportedException; /** * This class provides a basic implementation of the Attribute interface. *

* This implementation does not support the schema methods * getAttributeDefinition() and getAttributeSyntaxDefinition(). * They simply throw OperationNotSupportedException. * Subclasses of BasicAttribute should override these methods if they * support them. *

* The BasicAttribute class by default uses Object.equals() to * determine equality of attribute values when testing for equality or * when searching for values, except when the value is an array. * For an array, each element of the array is checked using Object.equals(). * Subclasses of BasicAttribute can make use of schema information * when doing similar equality checks by overriding methods * in which such use of schema is meaningful. * Similarly, the BasicAttribute class by default returns the values passed to its * constructor and/or manipulated using the add/remove methods. * Subclasses of BasicAttribute can override get() and getAll() * to get the values dynamically from the directory (or implement * the Attribute interface directly instead of subclassing BasicAttribute). *

* Note that updates to BasicAttribute (such as adding or removing a value) * does not affect the corresponding representation of the attribute * in the directory. Updates to the directory can only be effected * using operations in the DirContext interface. *

* A BasicAttribute instance is not synchronized against concurrent * multithreaded access. Multiple threads trying to access and modify a * BasicAttribute should lock the object. * * @author Rosanna Lee * @author Scott Seligman * @since 1.3 */ public class BasicAttribute implements Attribute { /** * Holds the attribute's id. It is initialized by the public constructor and * cannot be null unless methods in BasicAttribute that use attrID * have been overridden. * @serial */ protected String attrID; /** * Holds the attribute's values. Initialized by public constructors. * Cannot be null unless methods in BasicAttribute that use * values have been overridden. */ protected transient Vector values; /** * A flag for recording whether this attribute's values are ordered. * @serial */ protected boolean ordered = false; @SuppressWarnings("unchecked") public Object clone() { BasicAttribute attr; try { attr = (BasicAttribute)super.clone(); } catch (CloneNotSupportedException e) { attr = new BasicAttribute(attrID, ordered); } attr.values = (Vector)values.clone(); return attr; } /** * Determines whether obj is equal to this attribute. * Two attributes are equal if their attribute-ids, syntaxes * and values are equal. * If the attribute values are unordered, the order that the values were added * are irrelevant. If the attribute values are ordered, then the * order the values must match. * If obj is null or not an Attribute, false is returned. *

* By default Object.equals() is used when comparing the attribute * id and its values except when a value is an array. For an array, * each element of the array is checked using Object.equals(). * A subclass may override this to make * use of schema syntax information and matching rules, * which define what it means for two attributes to be equal. * How and whether a subclass makes * use of the schema information is determined by the subclass. * If a subclass overrides equals(), it should also override * hashCode() * such that two attributes that are equal have the same hash code. * * @param obj The possibly null object to check. * @return true if obj is equal to this attribute; false otherwise. * @see #hashCode * @see #contains */ public boolean equals(Object obj) { if ((obj != null) && (obj instanceof Attribute)) { Attribute target = (Attribute)obj; // Check order first if (isOrdered() != target.isOrdered()) { return false; } int len; if (attrID.equals(target.getID()) && (len=size()) == target.size()) { try { if (isOrdered()) { // Go through both list of values for (int i = 0; i < len; i++) { if (!valueEquals(get(i), target.get(i))) { return false; } } } else { // order is not relevant; check for existence Enumeration theirs = target.getAll(); while (theirs.hasMoreElements()) { if (find(theirs.nextElement()) < 0) return false; } } } catch (NamingException e) { return false; } return true; } } return false; } /** * Calculates the hash code of this attribute. *

* The hash code is computed by adding the hash code of * the attribute's id and that of all of its values except for * values that are arrays. * For an array, the hash code of each element of the array is summed. * If a subclass overrides hashCode(), it should override * equals() * as well so that two attributes that are equal have the same hash code. * * @return an int representing the hash code of this attribute. * @see #equals */ public int hashCode() { int hash = attrID.hashCode(); int num = values.size(); Object val; for (int i = 0; i < num; i ++) { val = values.elementAt(i); if (val != null) { if (val.getClass().isArray()) { Object it; int len = Array.getLength(val); for (int j = 0 ; j < len ; j++) { it = Array.get(val, j); if (it != null) { hash += it.hashCode(); } } } else { hash += val.hashCode(); } } } return hash; } /** * Generates the string representation of this attribute. * The string consists of the attribute's id and its values. * This string is meant for debugging and not meant to be * interpreted programmatically. * @return The non-null string representation of this attribute. */ public String toString() { StringBuilder answer = new StringBuilder(); answer.append(attrID).append(": "); if (values.size() == 0) { answer.append("No values"); } else { boolean start = true; for (Enumeration e = values.elements(); e.hasMoreElements(); ) { if (!start) answer.append(", "); answer.append(e.nextElement()); start = false; } } return answer.toString(); } /** * Constructs a new instance of an unordered attribute with no value. * * @param id The attribute's id. It cannot be null. */ public BasicAttribute(String id) { this(id, false); } /** * Constructs a new instance of an unordered attribute with a single value. * * @param id The attribute's id. It cannot be null. * @param value The attribute's value. If null, a null * value is added to the attribute. */ public BasicAttribute(String id, Object value) { this(id, value, false); } /** * Constructs a new instance of a possibly ordered attribute with no value. * * @param id The attribute's id. It cannot be null. * @param ordered true means the attribute's values will be ordered; * false otherwise. */ public BasicAttribute(String id, boolean ordered) { attrID = id; values = new Vector<>(); this.ordered = ordered; } /** * Constructs a new instance of a possibly ordered attribute with a * single value. * * @param id The attribute's id. It cannot be null. * @param value The attribute's value. If null, a null * value is added to the attribute. * @param ordered true means the attribute's values will be ordered; * false otherwise. */ public BasicAttribute(String id, Object value, boolean ordered) { this(id, ordered); values.addElement(value); } /** * Retrieves an enumeration of this attribute's values. *

* By default, the values returned are those passed to the * constructor and/or manipulated using the add/replace/remove methods. * A subclass may override this to retrieve the values dynamically * from the directory. */ public NamingEnumeration getAll() throws NamingException { return new ValuesEnumImpl(); } /** * Retrieves one of this attribute's values. *

* By default, the value returned is one of those passed to the * constructor and/or manipulated using the add/replace/remove methods. * A subclass may override this to retrieve the value dynamically * from the directory. */ public Object get() throws NamingException { if (values.size() == 0) { throw new NoSuchElementException("Attribute " + getID() + " has no value"); } else { return values.elementAt(0); } } public int size() { return values.size(); } public String getID() { return attrID; } /** * Determines whether a value is in this attribute. *

* By default, * Object.equals() is used when comparing attrVal * with this attribute's values except when attrVal is an array. * For an array, each element of the array is checked using * Object.equals(). * A subclass may use schema information to determine equality. */ public boolean contains(Object attrVal) { return (find(attrVal) >= 0); } // For finding first element that has a null in JDK1.1 Vector. // In the Java 2 platform, can just replace this with Vector.indexOf(target); private int find(Object target) { Class cl; if (target == null) { int ct = values.size(); for (int i = 0 ; i < ct ; i++) { if (values.elementAt(i) == null) return i; } } else if ((cl=target.getClass()).isArray()) { int ct = values.size(); Object it; for (int i = 0 ; i < ct ; i++) { it = values.elementAt(i); if (it != null && cl == it.getClass() && arrayEquals(target, it)) return i; } } else { return values.indexOf(target, 0); } return -1; // not found } /** * Determines whether two attribute values are equal. * Use arrayEquals for arrays and Object.equals() otherwise. */ private static boolean valueEquals(Object obj1, Object obj2) { if (obj1 == obj2) { return true; // object references are equal } if (obj1 == null) { return false; // obj2 was not false } if (obj1.getClass().isArray() && obj2.getClass().isArray()) { return arrayEquals(obj1, obj2); } return (obj1.equals(obj2)); } /** * Determines whether two arrays are equal by comparing each of their * elements using Object.equals(). */ private static boolean arrayEquals(Object a1, Object a2) { int len; if ((len = Array.getLength(a1)) != Array.getLength(a2)) return false; for (int j = 0; j < len; j++) { Object i1 = Array.get(a1, j); Object i2 = Array.get(a2, j); if (i1 == null || i2 == null) { if (i1 != i2) return false; } else if (!i1.equals(i2)) { return false; } } return true; } /** * Adds a new value to this attribute. *

* By default, Object.equals() is used when comparing attrVal * with this attribute's values except when attrVal is an array. * For an array, each element of the array is checked using * Object.equals(). * A subclass may use schema information to determine equality. */ public boolean add(Object attrVal) { if (isOrdered() || (find(attrVal) < 0)) { values.addElement(attrVal); return true; } else { return false; } } /** * Removes a specified value from this attribute. *

* By default, Object.equals() is used when comparing attrVal * with this attribute's values except when attrVal is an array. * For an array, each element of the array is checked using * Object.equals(). * A subclass may use schema information to determine equality. */ public boolean remove(Object attrval) { // For the Java 2 platform, can just use "return removeElement(attrval);" // Need to do the following to handle null case int i = find(attrval); if (i >= 0) { values.removeElementAt(i); return true; } return false; } public void clear() { values.setSize(0); } // ---- ordering methods public boolean isOrdered() { return ordered; } public Object get(int ix) throws NamingException { return values.elementAt(ix); } public Object remove(int ix) { Object answer = values.elementAt(ix); values.removeElementAt(ix); return answer; } public void add(int ix, Object attrVal) { if (!isOrdered() && contains(attrVal)) { throw new IllegalStateException( "Cannot add duplicate to unordered attribute"); } values.insertElementAt(attrVal, ix); } public Object set(int ix, Object attrVal) { if (!isOrdered() && contains(attrVal)) { throw new IllegalStateException( "Cannot add duplicate to unordered attribute"); } Object answer = values.elementAt(ix); values.setElementAt(attrVal, ix); return answer; } // ----------------- Schema methods /** * Retrieves the syntax definition associated with this attribute. *

* This method by default throws OperationNotSupportedException. A subclass * should override this method if it supports schema. */ public DirContext getAttributeSyntaxDefinition() throws NamingException { throw new OperationNotSupportedException("attribute syntax"); } /** * Retrieves this attribute's schema definition. *

* This method by default throws OperationNotSupportedException. A subclass * should override this method if it supports schema. */ public DirContext getAttributeDefinition() throws NamingException { throw new OperationNotSupportedException("attribute definition"); } // ---- serialization methods /** * Overridden to avoid exposing implementation details * @serialData Default field (the attribute ID -- a String), * followed by the number of values (an int), and the * individual values. */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { s.defaultWriteObject(); // write out the attrID s.writeInt(values.size()); for (int i = 0; i < values.size(); i++) { s.writeObject(values.elementAt(i)); } } /** * Overridden to avoid exposing implementation details. */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); // read in the attrID int n = s.readInt(); // number of values values = new Vector<>(n); while (--n >= 0) { values.addElement(s.readObject()); } } class ValuesEnumImpl implements NamingEnumeration { Enumeration list; ValuesEnumImpl() { list = values.elements(); } public boolean hasMoreElements() { return list.hasMoreElements(); } public Object nextElement() { return(list.nextElement()); } public Object next() throws NamingException { return list.nextElement(); } public boolean hasMore() throws NamingException { return list.hasMoreElements(); } public void close() throws NamingException { list = null; } } /** * Use serialVersionUID from JNDI 1.1.1 for interoperability. */ private static final long serialVersionUID = 6743528196119291326L; }