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 2010 Sun Microsystems, Inc.
015 * Portions copyright 2011-2015 ForgeRock AS.
016 */
017
018package org.forgerock.opendj.ldap;
019
020import java.util.Collection;
021import java.util.Iterator;
022
023import org.forgerock.i18n.LocalizedIllegalArgumentException;
024import org.forgerock.util.Reject;
025
026import com.forgerock.opendj.util.Iterators;
027
028/**
029 * This class contains methods for creating and manipulating attributes.
030 */
031public final class Attributes {
032
033    /**
034     * Empty attribute.
035     */
036    private static final class EmptyAttribute extends AbstractAttribute {
037
038        private final AttributeDescription attributeDescription;
039
040        private EmptyAttribute(final AttributeDescription attributeDescription) {
041            this.attributeDescription = attributeDescription;
042        }
043
044        @Override
045        public boolean add(final ByteString value) {
046            throw new UnsupportedOperationException();
047        }
048
049        @Override
050        public void clear() {
051            throw new UnsupportedOperationException();
052        }
053
054        @Override
055        public boolean contains(final Object value) {
056            return false;
057        }
058
059        @Override
060        public AttributeDescription getAttributeDescription() {
061            return attributeDescription;
062        }
063
064        @Override
065        public boolean isEmpty() {
066            return true;
067        }
068
069        @Override
070        public Iterator<ByteString> iterator() {
071            return Iterators.emptyIterator();
072        }
073
074        @Override
075        public boolean remove(final Object value) {
076            throw new UnsupportedOperationException();
077        }
078
079        @Override
080        public int size() {
081            return 0;
082        }
083
084    }
085
086    /**
087     * Renamed attribute.
088     */
089    private static final class RenamedAttribute implements Attribute {
090
091        private final Attribute attribute;
092        private final AttributeDescription attributeDescription;
093
094        private RenamedAttribute(final Attribute attribute,
095                final AttributeDescription attributeDescription) {
096            this.attribute = attribute;
097            this.attributeDescription = attributeDescription;
098        }
099
100        @Override
101        public boolean add(final ByteString value) {
102            return attribute.add(value);
103        }
104
105        @Override
106        public boolean add(final Object... values) {
107            return attribute.add(values);
108        }
109
110        @Override
111        public boolean addAll(final Collection<? extends ByteString> values) {
112            return attribute.addAll(values);
113        }
114
115        @Override
116        public <T> boolean addAll(final Collection<T> values,
117                final Collection<? super T> duplicateValues) {
118            return attribute.addAll(values, duplicateValues);
119        }
120
121        @Override
122        public void clear() {
123            attribute.clear();
124        }
125
126        @Override
127        public boolean contains(final Object value) {
128            return attribute.contains(value);
129        }
130
131        @Override
132        public boolean containsAll(final Collection<?> values) {
133            return attribute.containsAll(values);
134        }
135
136        @Override
137        public boolean equals(final Object object) {
138            return AbstractAttribute.equals(this, object);
139        }
140
141        @Override
142        public ByteString firstValue() {
143            return attribute.firstValue();
144        }
145
146        @Override
147        public String firstValueAsString() {
148            return attribute.firstValueAsString();
149        }
150
151        @Override
152        public AttributeDescription getAttributeDescription() {
153            return attributeDescription;
154        }
155
156        @Override
157        public String getAttributeDescriptionAsString() {
158            return attributeDescription.toString();
159        }
160
161        @Override
162        public int hashCode() {
163            return AbstractAttribute.hashCode(this);
164        }
165
166        @Override
167        public boolean isEmpty() {
168            return attribute.isEmpty();
169        }
170
171        @Override
172        public Iterator<ByteString> iterator() {
173            return attribute.iterator();
174        }
175
176        @Override
177        public AttributeParser parse() {
178            return attribute.parse();
179        }
180
181        @Override
182        public boolean remove(final Object value) {
183            return attribute.remove(value);
184        }
185
186        @Override
187        public boolean removeAll(final Collection<?> values) {
188            return attribute.removeAll(values);
189        }
190
191        @Override
192        public <T> boolean removeAll(final Collection<T> values,
193                final Collection<? super T> missingValues) {
194            return attribute.removeAll(values, missingValues);
195        }
196
197        @Override
198        public boolean retainAll(final Collection<?> values) {
199            return attribute.retainAll(values);
200        }
201
202        @Override
203        public <T> boolean retainAll(final Collection<T> values,
204                final Collection<? super T> missingValues) {
205            return attribute.retainAll(values, missingValues);
206        }
207
208        @Override
209        public int size() {
210            return attribute.size();
211        }
212
213        @Override
214        public ByteString[] toArray() {
215            return attribute.toArray();
216        }
217
218        @Override
219        public <T> T[] toArray(final T[] array) {
220            return attribute.toArray(array);
221        }
222
223        @Override
224        public String toString() {
225            return AbstractAttribute.toString(this);
226        }
227
228    }
229
230    /**
231     * Singleton attribute.
232     */
233    private static final class SingletonAttribute extends AbstractAttribute {
234
235        private final AttributeDescription attributeDescription;
236        private ByteString normalizedValue;
237        private final ByteString value;
238
239        private SingletonAttribute(final AttributeDescription attributeDescription,
240                final Object value) {
241            this.attributeDescription = attributeDescription;
242            this.value = ByteString.valueOfObject(value);
243        }
244
245        @Override
246        public boolean add(final ByteString value) {
247            throw new UnsupportedOperationException();
248        }
249
250        @Override
251        public void clear() {
252            throw new UnsupportedOperationException();
253        }
254
255        @Override
256        public boolean contains(final Object value) {
257            final ByteString normalizedValue = normalizeValue(this, ByteString.valueOfObject(value));
258            return normalizedSingleValue().equals(normalizedValue);
259        }
260
261        @Override
262        public AttributeDescription getAttributeDescription() {
263            return attributeDescription;
264        }
265
266        @Override
267        public boolean isEmpty() {
268            return false;
269        }
270
271        @Override
272        public Iterator<ByteString> iterator() {
273            return Iterators.singletonIterator(value);
274        }
275
276        @Override
277        public boolean remove(final Object value) {
278            throw new UnsupportedOperationException();
279        }
280
281        @Override
282        public int size() {
283            return 1;
284        }
285
286        /** Lazily computes the normalized single value. */
287        private ByteString normalizedSingleValue() {
288            if (normalizedValue == null) {
289                normalizedValue = normalizeValue(this, value);
290            }
291            return normalizedValue;
292        }
293
294    }
295
296    /**
297     * Unmodifiable attribute.
298     */
299    private static final class UnmodifiableAttribute implements Attribute {
300
301        private final Attribute attribute;
302
303        private UnmodifiableAttribute(final Attribute attribute) {
304            this.attribute = attribute;
305        }
306
307        @Override
308        public boolean add(final ByteString value) {
309            throw new UnsupportedOperationException();
310        }
311
312        @Override
313        public boolean add(final Object... values) {
314            throw new UnsupportedOperationException();
315        }
316
317        @Override
318        public boolean addAll(final Collection<? extends ByteString> values) {
319            throw new UnsupportedOperationException();
320        }
321
322        @Override
323        public <T> boolean addAll(final Collection<T> values,
324                final Collection<? super T> duplicateValues) {
325            throw new UnsupportedOperationException();
326        }
327
328        @Override
329        public void clear() {
330            throw new UnsupportedOperationException();
331        }
332
333        @Override
334        public boolean contains(final Object value) {
335            return attribute.contains(value);
336        }
337
338        @Override
339        public boolean containsAll(final Collection<?> values) {
340            return attribute.containsAll(values);
341        }
342
343        @Override
344        public boolean equals(final Object object) {
345            return object == this || attribute.equals(object);
346        }
347
348        @Override
349        public ByteString firstValue() {
350            return attribute.firstValue();
351        }
352
353        @Override
354        public String firstValueAsString() {
355            return attribute.firstValueAsString();
356        }
357
358        @Override
359        public AttributeDescription getAttributeDescription() {
360            return attribute.getAttributeDescription();
361        }
362
363        @Override
364        public String getAttributeDescriptionAsString() {
365            return attribute.getAttributeDescriptionAsString();
366        }
367
368        @Override
369        public int hashCode() {
370            return attribute.hashCode();
371        }
372
373        @Override
374        public boolean isEmpty() {
375            return attribute.isEmpty();
376        }
377
378        @Override
379        public Iterator<ByteString> iterator() {
380            return Iterators.unmodifiableIterator(attribute.iterator());
381        }
382
383        @Override
384        public AttributeParser parse() {
385            return attribute.parse();
386        }
387
388        @Override
389        public boolean remove(final Object value) {
390            throw new UnsupportedOperationException();
391        }
392
393        @Override
394        public boolean removeAll(final Collection<?> values) {
395            throw new UnsupportedOperationException();
396        }
397
398        @Override
399        public <T> boolean removeAll(final Collection<T> values,
400                final Collection<? super T> missingValues) {
401            throw new UnsupportedOperationException();
402        }
403
404        @Override
405        public boolean retainAll(final Collection<?> values) {
406            throw new UnsupportedOperationException();
407        }
408
409        @Override
410        public <T> boolean retainAll(final Collection<T> values,
411                final Collection<? super T> missingValues) {
412            throw new UnsupportedOperationException();
413        }
414
415        @Override
416        public int size() {
417            return attribute.size();
418        }
419
420        @Override
421        public ByteString[] toArray() {
422            return attribute.toArray();
423        }
424
425        @Override
426        public <T> T[] toArray(final T[] array) {
427            return attribute.toArray(array);
428        }
429
430        @Override
431        public String toString() {
432            return attribute.toString();
433        }
434    }
435
436    /**
437     * Returns a read-only empty attribute having the specified attribute
438     * description. Attempts to modify the returned attribute either directly,
439     * or indirectly via an iterator, result in an
440     * {@code UnsupportedOperationException}.
441     *
442     * @param attributeDescription
443     *            The attribute description.
444     * @return The empty attribute.
445     * @throws NullPointerException
446     *             If {@code attributeDescription} was {@code null}.
447     */
448    public static Attribute emptyAttribute(final AttributeDescription attributeDescription) {
449        return new EmptyAttribute(attributeDescription);
450    }
451
452    /**
453     * Returns a read-only empty attribute having the specified attribute
454     * description. The attribute description will be decoded using the default
455     * schema. Attempts to modify the returned attribute either directly, or
456     * indirectly via an iterator, result in an
457     * {@code UnsupportedOperationException}.
458     *
459     * @param attributeDescription
460     *            The attribute description.
461     * @return The empty attribute.
462     * @throws LocalizedIllegalArgumentException
463     *             If {@code attributeDescription} could not be decoded using
464     *             the default schema.
465     * @throws NullPointerException
466     *             If {@code attributeDescription} was {@code null}.
467     */
468    public static Attribute emptyAttribute(final String attributeDescription) {
469        return emptyAttribute(AttributeDescription.valueOf(attributeDescription));
470    }
471
472    /**
473     * Returns a view of {@code attribute} having a different attribute
474     * description. All operations on the returned attribute "pass-through" to
475     * the underlying attribute.
476     *
477     * @param attribute
478     *            The attribute to be renamed.
479     * @param attributeDescription
480     *            The new attribute description for {@code attribute}.
481     * @return A renamed view of {@code attribute}.
482     * @throws NullPointerException
483     *             If {@code attribute} or {@code attributeDescription} was
484     *             {@code null}.
485     */
486    public static Attribute renameAttribute(final Attribute attribute,
487            final AttributeDescription attributeDescription) {
488        Reject.ifNull(attribute, attributeDescription);
489
490        // Optimize for the case where no renaming is required.
491        if (attribute.getAttributeDescription() == attributeDescription) {
492            return attribute;
493        } else {
494            return new RenamedAttribute(attribute, attributeDescription);
495        }
496    }
497
498    /**
499     * Returns a view of {@code attribute} having a different attribute
500     * description. All operations on the returned attribute "pass-through" to
501     * the underlying attribute. The attribute description will be decoded using
502     * the default schema.
503     *
504     * @param attribute
505     *            The attribute to be renamed.
506     * @param attributeDescription
507     *            The new attribute description for {@code attribute}.
508     * @return A renamed view of {@code attribute}.
509     * @throws LocalizedIllegalArgumentException
510     *             If {@code attributeDescription} could not be decoded using
511     *             the default schema.
512     * @throws NullPointerException
513     *             If {@code attribute} or {@code attributeDescription} was
514     *             {@code null}.
515     */
516    public static Attribute renameAttribute(final Attribute attribute, final String attributeDescription) {
517        Reject.ifNull(attribute, attributeDescription);
518        return renameAttribute(attribute, AttributeDescription.valueOf(attributeDescription));
519    }
520
521    /**
522     * Returns a read-only single-valued attribute having the specified
523     * attribute description and value. Attempts to modify the returned
524     * attribute either directly, or indirectly via an iterator, result in an
525     * {@code UnsupportedOperationException}.
526     * <p>
527     * If {@code value} is not an instance of {@code ByteString} then it will be
528     * converted using the {@link ByteString#valueOfObject(Object)} method.
529     *
530     * @param attributeDescription
531     *            The attribute description.
532     * @param value
533     *            The single attribute value.
534     * @return The single-valued attribute.
535     * @throws NullPointerException
536     *             If {@code attributeDescription} or {@code value} was
537     *             {@code null}.
538     */
539    public static Attribute singletonAttribute(final AttributeDescription attributeDescription, final Object value) {
540        return new SingletonAttribute(attributeDescription, value);
541    }
542
543    /**
544     * Returns a read-only single-valued attribute having the specified
545     * attribute description. The attribute description will be decoded using
546     * the default schema. Attempts to modify the returned attribute either
547     * directly, or indirectly via an iterator, result in an
548     * {@code UnsupportedOperationException}.
549     * <p>
550     * If {@code value} is not an instance of {@code ByteString} then it will be
551     * converted using the {@link ByteString#valueOfObject(Object)} method.
552     *
553     * @param attributeDescription
554     *            The attribute description.
555     * @param value
556     *            The single attribute value.
557     * @return The single-valued attribute.
558     * @throws LocalizedIllegalArgumentException
559     *             If {@code attributeDescription} could not be decoded using
560     *             the default schema.
561     * @throws NullPointerException
562     *             If {@code attributeDescription} or {@code value} was
563     *             {@code null}.
564     */
565    public static Attribute singletonAttribute(final String attributeDescription, final Object value) {
566        return singletonAttribute(AttributeDescription.valueOf(attributeDescription), value);
567    }
568
569    /**
570     * Returns a read-only view of {@code attribute}. Query operations on the
571     * returned attribute "read-through" to the underlying attribute, and
572     * attempts to modify the returned attribute either directly or indirectly
573     * via an iterator result in an {@code UnsupportedOperationException}.
574     *
575     * @param attribute
576     *            The attribute for which a read-only view is to be returned.
577     * @return A read-only view of {@code attribute}.
578     * @throws NullPointerException
579     *             If {@code attribute} was {@code null}.
580     */
581    public static Attribute unmodifiableAttribute(final Attribute attribute) {
582        if (attribute instanceof UnmodifiableAttribute) {
583            return attribute;
584        }
585        return new UnmodifiableAttribute(attribute);
586    }
587
588    /** Prevent instantiation. */
589    private Attributes() {
590        // Nothing to do.
591    }
592}