001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2009 Sun Microsystems, Inc.
015 * Portions copyright 2012-2016 ForgeRock AS.
016 */
017package org.forgerock.opendj.ldap;
018
019import java.util.AbstractSet;
020import java.util.Collection;
021import java.util.HashMap;
022import java.util.Iterator;
023import java.util.Map;
024
025import org.forgerock.opendj.ldap.schema.AttributeType;
026import org.forgerock.opendj.ldap.schema.MatchingRule;
027
028import org.forgerock.util.Reject;
029
030/**
031 * This class provides a skeletal implementation of the {@code Attribute}
032 * interface, to minimize the effort required to implement this interface.
033 */
034public abstract class AbstractAttribute extends AbstractSet<ByteString> implements Attribute {
035
036    /**
037     * Returns {@code true} if {@code object} is an attribute which is equal to
038     * {@code attribute}. Two attributes are considered equal if their attribute
039     * descriptions are equal, they both have the same number of attribute
040     * values, and every attribute value contained in the first attribute is
041     * also contained in the second attribute.
042     *
043     * @param attribute
044     *            The attribute to be tested for equality.
045     * @param object
046     *            The object to be tested for equality with the attribute.
047     * @return {@code true} if {@code object} is an attribute which is equal to
048     *         {@code attribute}, or {@code false} if not.
049     */
050    static boolean equals(final Attribute attribute, final Object object) {
051        if (attribute == object) {
052            return true;
053        }
054        if (!(object instanceof Attribute)) {
055            return false;
056        }
057
058        final Attribute other = (Attribute) object;
059        return attribute.getAttributeDescription().equals(other.getAttributeDescription())
060                && attribute.size() == other.size()
061                && attribute.containsAll(other);
062    }
063
064    /**
065     * Returns the hash code for {@code attribute}. It will be calculated as the
066     * sum of the hash codes of the attribute description and all of the
067     * attribute values.
068     *
069     * @param attribute
070     *            The attribute whose hash code should be calculated.
071     * @return The hash code for {@code attribute}.
072     */
073    static int hashCode(final Attribute attribute) {
074        int hashCode = attribute.getAttributeDescription().hashCode();
075        for (final ByteString value : attribute) {
076            hashCode += normalizeValue(attribute, value).hashCode();
077        }
078        return hashCode;
079    }
080
081    /**
082     * Returns the normalized form of {@code value} normalized using
083     * {@code attribute}'s equality matching rule.
084     *
085     * @param attribute
086     *            The attribute whose equality matching rule should be used for
087     *            normalization.
088     * @param value
089     *            The attribute value to be normalized.
090     * @return The normalized form of {@code value} normalized using
091     *         {@code attribute}'s equality matching rule.
092     */
093    static ByteString normalizeValue(final Attribute attribute, final ByteString value) {
094        final AttributeDescription attributeDescription = attribute.getAttributeDescription();
095        final AttributeType attributeType = attributeDescription.getAttributeType();
096        final MatchingRule matchingRule = attributeType.getEqualityMatchingRule();
097
098        try {
099            return matchingRule.normalizeAttributeValue(value);
100        } catch (final Exception e) {
101            // Fall back to provided value.
102            return value;
103        }
104    }
105
106    /**
107     * Returns a string representation of {@code attribute}.
108     *
109     * @param attribute
110     *            The attribute whose string representation should be returned.
111     * @return The string representation of {@code attribute}.
112     */
113    static String toString(final Attribute attribute) {
114        final StringBuilder builder = new StringBuilder();
115        builder.append('"');
116        builder.append(attribute.getAttributeDescriptionAsString());
117        builder.append("\":[");
118        boolean firstValue = true;
119        for (final ByteString value : attribute) {
120            if (!firstValue) {
121                builder.append(',');
122            }
123            builder.append('"');
124            builder.append(value);
125            builder.append('"');
126            firstValue = false;
127        }
128        builder.append(']');
129        return builder.toString();
130    }
131
132    /** Sole constructor. */
133    protected AbstractAttribute() {
134        // No implementation required.
135    }
136
137    @Override
138    public abstract boolean add(ByteString value);
139
140    @Override
141    public boolean add(final Object... values) {
142        Reject.ifNull(values);
143        boolean modified = false;
144        for (final Object value : values) {
145            modified |= add(ByteString.valueOfObject(value));
146        }
147        return modified;
148    }
149
150    @Override
151    public boolean addAll(final Collection<? extends ByteString> values) {
152        return addAll(values, null);
153    }
154
155    @Override
156    public <T> boolean addAll(final Collection<T> values,
157            final Collection<? super T> duplicateValues) {
158        boolean modified = false;
159        for (final T value : values) {
160            if (add(value)) {
161                modified = true;
162            } else if (duplicateValues != null) {
163                duplicateValues.add(value);
164            }
165        }
166        return modified;
167    }
168
169    @Override
170    public abstract boolean contains(Object value);
171
172    @Override
173    public boolean containsAll(final Collection<?> values) {
174        for (final Object value : values) {
175            if (!contains(value)) {
176                return false;
177            }
178        }
179        return true;
180    }
181
182    @Override
183    public boolean equals(final Object object) {
184        return equals(this, object);
185    }
186
187    @Override
188    public ByteString firstValue() {
189        return iterator().next();
190    }
191
192    @Override
193    public String firstValueAsString() {
194        return firstValue().toString();
195    }
196
197    @Override
198    public abstract AttributeDescription getAttributeDescription();
199
200    @Override
201    public String getAttributeDescriptionAsString() {
202        return getAttributeDescription().toString();
203    }
204
205    @Override
206    public int hashCode() {
207        return hashCode(this);
208    }
209
210    @Override
211    public AttributeParser parse() {
212        return AttributeParser.parseAttribute(this);
213    }
214
215    @Override
216    public abstract Iterator<ByteString> iterator();
217
218    @Override
219    public abstract boolean remove(Object value);
220
221    @Override
222    public boolean removeAll(final Collection<?> values) {
223        return removeAll(values, null);
224    }
225
226    @Override
227    public <T> boolean removeAll(final Collection<T> values,
228            final Collection<? super T> missingValues) {
229        boolean modified = false;
230        for (final T value : values) {
231            if (remove(value)) {
232                modified = true;
233            } else if (missingValues != null) {
234                missingValues.add(value);
235            }
236        }
237        return modified;
238    }
239
240    @Override
241    public boolean retainAll(final Collection<?> values) {
242        return retainAll(values, null);
243    }
244
245    @Override
246    public <T> boolean retainAll(final Collection<T> values,
247            final Collection<? super T> missingValues) {
248        if (values.isEmpty()) {
249            if (isEmpty()) {
250                return false;
251            } else {
252                clear();
253                return true;
254            }
255        }
256
257        if (isEmpty()) {
258            if (missingValues != null) {
259                missingValues.addAll(values);
260            }
261            return false;
262        }
263
264        final Map<ByteString, T> valuesToRetain = new HashMap<>(values.size());
265        for (final T value : values) {
266            valuesToRetain.put(normalizeValue(this, ByteString.valueOfObject(value)), value);
267        }
268
269        boolean modified = false;
270        final Iterator<ByteString> iterator = iterator();
271        while (iterator.hasNext()) {
272            final ByteString value = iterator.next();
273            final ByteString normalizedValue = normalizeValue(this, value);
274            if (valuesToRetain.remove(normalizedValue) == null) {
275                modified = true;
276                iterator.remove();
277            }
278        }
279
280        if (missingValues != null) {
281            missingValues.addAll(valuesToRetain.values());
282        }
283
284        return modified;
285    }
286
287    @Override
288    public abstract int size();
289
290    @Override
291    public ByteString[] toArray() {
292        return toArray(new ByteString[size()]);
293    }
294
295    @Override
296    public String toString() {
297        return toString(this);
298    }
299
300}