001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2009 Sun Microsystems, Inc.
025 *      Portions copyright 2011-2015 ForgeRock AS.
026 */
027
028package org.forgerock.opendj.ldap.schema;
029
030import static java.util.Arrays.*;
031
032import static org.forgerock.opendj.ldap.schema.SchemaConstants.*;
033import static org.forgerock.opendj.ldap.schema.SchemaUtils.*;
034
035import static com.forgerock.opendj.ldap.CoreMessages.*;
036import static com.forgerock.opendj.util.StaticUtils.*;
037
038import java.util.Collection;
039import java.util.Collections;
040import java.util.Iterator;
041import java.util.LinkedList;
042import java.util.List;
043import java.util.Map;
044
045import org.forgerock.i18n.LocalizableMessage;
046import org.forgerock.util.Reject;
047
048import com.forgerock.opendj.util.StaticUtils;
049
050/**
051 * This class defines a data structure for storing and interacting with an
052 * attribute type, which contains information about the format of an attribute
053 * and the syntax and matching rules that should be used when interacting with
054 * it.
055 * <p>
056 * Where ordered sets of names, or extra properties are provided, the ordering
057 * will be preserved when the associated fields are accessed via their getters
058 * or via the {@link #toString()} methods.
059 */
060public final class AttributeType extends SchemaElement implements Comparable<AttributeType> {
061
062    /** A fluent API for incrementally constructing attribute type. */
063    public static final class Builder extends SchemaElementBuilder<Builder> {
064        private String oid;
065        private final List<String> names = new LinkedList<>();
066        private AttributeUsage attributeUsage;
067        private boolean isCollective;
068        private boolean isNoUserModification;
069        private boolean isObsolete;
070        private boolean isSingleValue;
071        private String approximateMatchingRuleOID;
072        private String equalityMatchingRuleOID;
073        private String orderingMatchingRuleOID;
074        private String substringMatchingRuleOID;
075        private String superiorTypeOID;
076        private String syntaxOID;
077
078        Builder(final AttributeType at, final SchemaBuilder builder) {
079            super(builder, at);
080            this.oid = at.oid;
081            this.attributeUsage = at.attributeUsage;
082            this.isCollective = at.isCollective;
083            this.isNoUserModification = at.isNoUserModification;
084            this.isObsolete = at.isObsolete;
085            this.isSingleValue = at.isSingleValue;
086            this.names.addAll(at.names);
087            this.approximateMatchingRuleOID = at.approximateMatchingRuleOID;
088            this.equalityMatchingRuleOID = at.equalityMatchingRuleOID;
089            this.orderingMatchingRuleOID = at.orderingMatchingRuleOID;
090            this.substringMatchingRuleOID = at.substringMatchingRuleOID;
091            this.superiorTypeOID = at.superiorTypeOID;
092            this.syntaxOID = at.syntaxOID;
093        }
094
095        Builder(final String oid, final SchemaBuilder builder) {
096            super(builder);
097            this.oid = oid;
098        }
099
100        /**
101         * Adds this attribute type to the schema, throwing a
102         * {@code ConflictingSchemaElementException} if there is an existing
103         * attribute type with the same numeric OID.
104         *
105         * @return The parent schema builder.
106         * @throws ConflictingSchemaElementException
107         *             If there is an existing attribute type with the same
108         *             numeric OID.
109         */
110        public SchemaBuilder addToSchema() {
111            return getSchemaBuilder().addAttributeType(new AttributeType(this), false);
112        }
113
114        /**
115         * Adds this attribute type to the schema overwriting any existing
116         * attribute type with the same numeric OID.
117         *
118         * @return The parent schema builder.
119         */
120        public SchemaBuilder addToSchemaOverwrite() {
121            return getSchemaBuilder().addAttributeType(new AttributeType(this), true);
122        }
123
124        /**
125         * Sets the matching rule that should be used for approximate matching
126         * with this attribute type.
127         *
128         * @param approximateMatchingRuleOID
129         *            The matching rule OID.
130         * @return This builder.
131         */
132        public Builder approximateMatchingRule(String approximateMatchingRuleOID) {
133            this.approximateMatchingRuleOID = approximateMatchingRuleOID;
134            return this;
135        }
136
137        /**
138         * Specifies whether this attribute type is "collective".
139         *
140         * @param isCollective
141         *            {@code true} if this attribute type is "collective".
142         * @return This builder.
143         */
144        public Builder collective(boolean isCollective) {
145            this.isCollective = isCollective;
146            return this;
147        }
148
149        @Override
150        public Builder description(String description) {
151            return description0(description);
152        }
153
154        /**
155         * Sets the matching rule that should be used for equality matching with
156         * this attribute type.
157         *
158         * @param equalityMatchingRuleOID
159         *            The matching rule OID.
160         * @return This builder.
161         */
162        public Builder equalityMatchingRule(String equalityMatchingRuleOID) {
163            this.equalityMatchingRuleOID = equalityMatchingRuleOID;
164            return this;
165        }
166
167        @Override
168        public Builder extraProperties(Map<String, List<String>> extraProperties) {
169            return extraProperties0(extraProperties);
170        }
171
172        @Override
173        public Builder extraProperties(String extensionName, String... extensionValues) {
174            return extraProperties0(extensionName, extensionValues);
175        }
176
177        @Override
178        Builder getThis() {
179            return this;
180        }
181
182        /**
183         * Adds the provided user friendly names.
184         *
185         * @param names
186         *            The user friendly names.
187         * @return This builder.
188         */
189        public Builder names(final Collection<String> names) {
190            this.names.addAll(names);
191            return this;
192        }
193
194        /**
195         * Adds the provided user friendly names.
196         *
197         * @param names
198         *            The user friendly names.
199         * @return This builder.
200         */
201        public Builder names(final String... names) {
202            return names(asList(names));
203        }
204
205        /**
206         * Specifies whether this attribute type is "no-user-modification".
207         *
208         * @param isNoUserModification
209         *            {@code true} if this attribute type is
210         *            "no-user-modification"
211         * @return This builder.
212         */
213        public Builder noUserModification(boolean isNoUserModification) {
214            this.isNoUserModification = isNoUserModification;
215            return this;
216        }
217
218        /**
219         * Specifies whether this schema element is obsolete.
220         *
221         * @param isObsolete
222         *            {@code true} if this schema element is obsolete (default
223         *            is {@code false}).
224         * @return This builder.
225         */
226        public Builder obsolete(final boolean isObsolete) {
227            this.isObsolete = isObsolete;
228            return this;
229        }
230
231        /**
232         * Sets the numeric OID which uniquely identifies this attribute type.
233         *
234         * @param oid
235         *            The numeric OID.
236         * @return This builder.
237         */
238        public Builder oid(final String oid) {
239            this.oid = oid;
240            return this;
241        }
242
243        /**
244         * Sets the matching rule that should be used for ordering with this
245         * attribute type.
246         *
247         * @param orderingMatchingRuleOID
248         *            The matching rule OID.
249         * @return This Builder.
250         */
251        public Builder orderingMatchingRule(String orderingMatchingRuleOID) {
252            this.orderingMatchingRuleOID = orderingMatchingRuleOID;
253            return this;
254        }
255
256        @Override
257        public Builder removeAllExtraProperties() {
258            return removeAllExtraProperties0();
259        }
260
261        /**
262         * Removes all user defined names.
263         *
264         * @return This builder.
265         */
266        public Builder removeAllNames() {
267            this.names.clear();
268            return this;
269        }
270
271        @Override
272        public Builder removeExtraProperty(String extensionName, String... extensionValues) {
273            return removeExtraProperty0(extensionName, extensionValues);
274        }
275
276        /**
277         * Removes the provided user defined name.
278         *
279         * @param name
280         *            The user defined name to be removed.
281         * @return This builder.
282         */
283        public Builder removeName(String name) {
284            this.names.remove(name);
285            return this;
286        }
287
288        /**
289         * Specifies whether this attribute type is declared "single-value".
290         *
291         * @param isSingleValue
292         *            {@code true} if this attribute type is declared
293         *            "single-value".
294         * @return This builder.
295         */
296        public Builder singleValue(boolean isSingleValue) {
297            this.isSingleValue = isSingleValue;
298            return this;
299        }
300
301        /**
302         * Sets the matching rule that should be used for substring matching
303         * with this attribute type.
304         *
305         * @param substringMatchingRuleOID
306         *            The matching rule OID.
307         * @return This builder.
308         */
309        public Builder substringMatchingRule(String substringMatchingRuleOID) {
310            this.substringMatchingRuleOID = substringMatchingRuleOID;
311            return this;
312        }
313
314        /**
315         * Sets the superior type for this attribute type.
316         *
317         * @param superiorTypeOID
318         *            The superior type OID.
319         * @return This builder.
320         */
321        public Builder superiorType(String superiorTypeOID) {
322            this.superiorTypeOID = superiorTypeOID;
323            return this;
324        }
325
326        /**
327         * Sets the syntax for this attribute type.
328         *
329         * @param syntaxOID
330         *            The syntax OID.
331         * @return This builder.
332         */
333        public Builder syntax(String syntaxOID) {
334            this.syntaxOID = syntaxOID;
335            return this;
336        }
337
338        /**
339         * Sets the usage indicator for this attribute type.
340         *
341         * @param attributeUsage
342         *            The attribute usage.
343         * @return This builder.
344         */
345        public Builder usage(AttributeUsage attributeUsage) {
346            this.attributeUsage = attributeUsage;
347            return this;
348        }
349    }
350
351    /** The approximate matching rule for this attribute type. */
352    private final String approximateMatchingRuleOID;
353
354    /** The attribute usage for this attribute type. */
355    private final AttributeUsage attributeUsage;
356
357    /** The equality matching rule for this attribute type. */
358    private final String equalityMatchingRuleOID;
359
360    /** Indicates whether this attribute type is declared "collective". */
361    private final boolean isCollective;
362
363    /** Indicates whether this attribute type is declared "no-user-modification". */
364    private final boolean isNoUserModification;
365
366    /** Indicates whether this definition is declared "obsolete". */
367    private final boolean isObsolete;
368
369    /** Indicates whether this definition is a temporary place-holder. */
370    private final boolean isPlaceHolder;
371
372    /** Indicates whether this attribute type is declared "single-value". */
373    private final boolean isSingleValue;
374
375    /** The set of user defined names for this definition. */
376    private final List<String> names;
377
378    /** The OID that may be used to reference this definition. */
379    private final String oid;
380
381    /** The ordering matching rule for this attribute type. */
382    private final String orderingMatchingRuleOID;
383
384    /** The substring matching rule for this attribute type. */
385    private final String substringMatchingRuleOID;
386
387    /** The superior attribute type from which this attribute type inherits. */
388    private final String superiorTypeOID;
389
390    /** The syntax for this attribute type. */
391    private final String syntaxOID;
392
393    /** True if this type has OID 2.5.4.0. */
394    private final boolean isObjectClassType;
395
396    /** The normalized name of this attribute type. */
397    private final String normalizedName;
398
399    /** The superior attribute type from which this attribute type inherits. */
400    private AttributeType superiorType;
401
402    /** The equality matching rule for this attribute type. */
403    private MatchingRule equalityMatchingRule;
404
405    /** The ordering matching rule for this attribute type. */
406    private MatchingRule orderingMatchingRule;
407
408    /** The substring matching rule for this attribute type. */
409    private MatchingRule substringMatchingRule;
410
411    /** The approximate matching rule for this attribute type. */
412    private MatchingRule approximateMatchingRule;
413
414    /** The syntax for this attribute type. */
415    private Syntax syntax;
416
417    /** Indicates whether or not validation has been performed. */
418    private boolean needsValidating = true;
419
420    /** The indicates whether or not validation failed. */
421    private boolean isValid;
422
423    private AttributeType(Builder builder) {
424        super(builder);
425        Reject.ifTrue(builder.oid == null || builder.oid.isEmpty(), "An OID must be specified.");
426        Reject.ifTrue(builder.superiorTypeOID == null && builder.syntaxOID == null,
427                "Superior type and/or Syntax must not be null");
428
429        oid = builder.oid;
430        names = unmodifiableCopyOfList(builder.names);
431        attributeUsage = builder.attributeUsage;
432        isCollective = builder.isCollective;
433        isNoUserModification = builder.isNoUserModification;
434        isObjectClassType = "2.5.4.0".equals(oid);
435        isObsolete = builder.isObsolete;
436        isSingleValue = builder.isSingleValue;
437        approximateMatchingRuleOID = builder.approximateMatchingRuleOID;
438        equalityMatchingRuleOID = builder.equalityMatchingRuleOID;
439        orderingMatchingRuleOID = builder.orderingMatchingRuleOID;
440        substringMatchingRuleOID = builder.substringMatchingRuleOID;
441        superiorTypeOID = builder.superiorTypeOID;
442        syntaxOID = builder.syntaxOID;
443        isPlaceHolder = false;
444        normalizedName = toLowerCase(getNameOrOID());
445    }
446
447    /**
448     * Creates a new place-holder attribute type having the specified name,
449     * default syntax, and default matching rule. The OID of the place-holder
450     * attribute will be the normalized attribute type name followed by the
451     * suffix "-oid".
452     *
453     * @param schema
454     *            The parent schema.
455     * @param name
456     *            The name of the place-holder attribute type.
457     */
458    AttributeType(final Schema schema, final String name) {
459        final StringBuilder builder = new StringBuilder(name.length() + 4);
460        StaticUtils.toLowerCase(name, builder);
461        builder.append("-oid");
462
463        this.oid = builder.toString();
464        this.names = Collections.singletonList(name);
465        this.isObsolete = false;
466        this.superiorTypeOID = null;
467        this.superiorType = null;
468        this.equalityMatchingRule = schema.getDefaultMatchingRule();
469        this.equalityMatchingRuleOID = equalityMatchingRule.getOID();
470        this.orderingMatchingRuleOID = null;
471        this.substringMatchingRuleOID = null;
472        this.approximateMatchingRuleOID = null;
473        this.syntax = schema.getDefaultSyntax();
474        this.syntaxOID = syntax.getOID();
475        this.isSingleValue = false;
476        this.isCollective = false;
477        this.isNoUserModification = false;
478        this.attributeUsage = null;
479        this.isObjectClassType = false;
480        this.isPlaceHolder = true;
481        this.normalizedName = StaticUtils.toLowerCase(getNameOrOID());
482    }
483
484    /**
485     * Compares this attribute type to the provided attribute type. The
486     * sort-order is defined as follows:
487     * <ul>
488     * <li>The {@code objectClass} attribute is less than all other attribute
489     * types.
490     * <li>User attributes are less than operational attributes.
491     * <li>Lexicographic comparison of the primary name and then, if equal, the
492     * OID.
493     * </ul>
494     *
495     * @param type
496     *            The attribute type to be compared.
497     * @return A negative integer, zero, or a positive integer as this attribute
498     *         type is less than, equal to, or greater than the specified
499     *         attribute type.
500     * @throws NullPointerException
501     *             If {@code name} was {@code null}.
502     */
503    public int compareTo(final AttributeType type) {
504        if (isObjectClassType) {
505            return type.isObjectClassType ? 0 : -1;
506        } else if (type.isObjectClassType) {
507            return 1;
508        } else {
509            final boolean isOperational = getUsage().isOperational();
510            final boolean typeIsOperational = type.getUsage().isOperational();
511            if (isOperational == typeIsOperational) {
512                final int tmp = normalizedName.compareTo(type.normalizedName);
513                if (tmp == 0) {
514                    return oid.compareTo(type.oid);
515                } else {
516                    return tmp;
517                }
518            } else {
519                return isOperational ? 1 : -1;
520            }
521        }
522    }
523
524    /**
525     * Returns {@code true} if the provided object is an attribute type having
526     * the same numeric OID as this attribute type.
527     *
528     * @param o
529     *            The object to be compared.
530     * @return {@code true} if the provided object is an attribute type having
531     *         the same numeric OID as this attribute type.
532     */
533    @Override
534    public boolean equals(final Object o) {
535        if (this == o) {
536            return true;
537        } else if (o instanceof AttributeType) {
538            final AttributeType other = (AttributeType) o;
539            return oid.equals(other.oid);
540        } else {
541            return false;
542        }
543    }
544
545    /**
546     * Returns the matching rule that should be used for approximate matching
547     * with this attribute type.
548     *
549     * @return The matching rule that should be used for approximate matching
550     *         with this attribute type.
551     */
552    public MatchingRule getApproximateMatchingRule() {
553        return approximateMatchingRule;
554    }
555
556    /**
557     * Returns the matching rule that should be used for equality matching with
558     * this attribute type.
559     *
560     * @return The matching rule that should be used for equality matching with
561     *         this attribute type.
562     */
563    public MatchingRule getEqualityMatchingRule() {
564        return equalityMatchingRule;
565    }
566
567    /**
568     * Returns the name or OID for this schema definition. If it has one or more
569     * names, then the primary name will be returned. If it does not have any
570     * names, then the OID will be returned.
571     *
572     * @return The name or OID for this schema definition.
573     */
574    public String getNameOrOID() {
575        if (names.isEmpty()) {
576            return oid;
577        }
578        return names.get(0);
579    }
580
581    /**
582     * Returns an unmodifiable list containing the user-defined names that may
583     * be used to reference this schema definition.
584     *
585     * @return Returns an unmodifiable list containing the user-defined names
586     *         that may be used to reference this schema definition.
587     */
588    public List<String> getNames() {
589        return names;
590    }
591
592    /**
593     * Returns the OID for this schema definition.
594     *
595     * @return The OID for this schema definition.
596     */
597    public String getOID() {
598        return oid;
599    }
600
601    /**
602     * Returns the matching rule that should be used for ordering with this
603     * attribute type.
604     *
605     * @return The matching rule that should be used for ordering with this
606     *         attribute type.
607     */
608    public MatchingRule getOrderingMatchingRule() {
609        return orderingMatchingRule;
610    }
611
612    /**
613     * Returns the matching rule that should be used for substring matching with
614     * this attribute type.
615     *
616     * @return The matching rule that should be used for substring matching with
617     *         this attribute type.
618     */
619    public MatchingRule getSubstringMatchingRule() {
620        return substringMatchingRule;
621    }
622
623    /**
624     * Returns the superior type for this attribute type.
625     *
626     * @return The superior type for this attribute type, or <CODE>null</CODE>
627     *         if it does not have one.
628     */
629    public AttributeType getSuperiorType() {
630        return superiorType;
631    }
632
633    /**
634     * Returns the syntax for this attribute type.
635     *
636     * @return The syntax for this attribute type.
637     */
638    public Syntax getSyntax() {
639        return syntax;
640    }
641
642    /**
643     * Returns the usage indicator for this attribute type.
644     *
645     * @return The usage indicator for this attribute type.
646     */
647    public AttributeUsage getUsage() {
648        return attributeUsage != null ? attributeUsage : AttributeUsage.USER_APPLICATIONS;
649    }
650
651    /**
652     * Returns the hash code for this attribute type. It will be calculated as
653     * the hash code of the numeric OID.
654     *
655     * @return The hash code for this attribute type.
656     */
657    @Override
658    public int hashCode() {
659        return oid.hashCode();
660    }
661
662    /**
663     * Indicates whether this schema definition has the specified name.
664     *
665     * @param name
666     *            The name for which to make the determination.
667     * @return {@code true} if the specified name is assigned to this schema
668     *         definition, or {@code false} if not.
669     */
670    public boolean hasName(final String name) {
671        for (final String n : names) {
672            if (n.equalsIgnoreCase(name)) {
673                return true;
674            }
675        }
676        return false;
677    }
678
679    /**
680     * Indicates whether this schema definition has the specified name or OID.
681     *
682     * @param value
683     *            The value for which to make the determination.
684     * @return {@code true} if the provided value matches the OID or one of the
685     *         names assigned to this schema definition, or {@code false} if
686     *         not.
687     */
688    public boolean hasNameOrOID(final String value) {
689        return hasName(value) || getOID().equals(value);
690    }
691
692    /**
693     * Indicates whether this attribute type is declared "collective".
694     *
695     * @return {@code true} if this attribute type is declared "collective", or
696     *         {@code false} if not.
697     */
698    public boolean isCollective() {
699        return isCollective;
700    }
701
702    /**
703     * Indicates whether this attribute type is declared "no-user-modification".
704     *
705     * @return {@code true} if this attribute type is declared
706     *         "no-user-modification", or {@code false} if not.
707     */
708    public boolean isNoUserModification() {
709        return isNoUserModification;
710    }
711
712    /**
713     * Indicates whether or not this attribute type is the {@code objectClass}
714     * attribute type having the OID 2.5.4.0.
715     *
716     * @return {@code true} if this attribute type is the {@code objectClass}
717     *         attribute type, or {@code false} if not.
718     */
719    public boolean isObjectClass() {
720        return isObjectClassType;
721    }
722
723    /**
724     * Indicates whether this schema definition is declared "obsolete".
725     *
726     * @return {@code true} if this schema definition is declared "obsolete", or
727     *         {@code false} if not.
728     */
729    public boolean isObsolete() {
730        return isObsolete;
731    }
732
733    /**
734     * Indicates whether this is an operational attribute. An operational
735     * attribute is one with a usage of "directoryOperation",
736     * "distributedOperation", or "dSAOperation" (i.e., only userApplications is
737     * not operational).
738     *
739     * @return {@code true} if this is an operational attribute, or
740     *         {@code false} if not.
741     */
742    public boolean isOperational() {
743        return getUsage().isOperational();
744    }
745
746    /**
747     * Indicates whether this attribute type is a temporary place-holder
748     * allocated dynamically by a non-strict schema when no registered attribute
749     * type was found.
750     * <p>
751     * Place holder attribute types have an OID which is the normalized
752     * attribute name with the string {@code -oid} appended. In addition, they
753     * will use the directory string syntax and case ignore matching rule.
754     *
755     * @return {@code true} if this is a temporary place-holder attribute type
756     *         allocated dynamically by a non-strict schema when no registered
757     *         attribute type was found.
758     * @see Schema#getAttributeType(String)
759     */
760    public boolean isPlaceHolder() {
761        return isPlaceHolder;
762    }
763
764    /**
765     * Indicates whether this attribute type is declared "single-value".
766     *
767     * @return {@code true} if this attribute type is declared "single-value",
768     *         or {@code false} if not.
769     */
770    public boolean isSingleValue() {
771        return isSingleValue;
772    }
773
774    /**
775     * Indicates whether or not this attribute type is a sub-type of the
776     * provided attribute type.
777     *
778     * @param type
779     *            The attribute type for which to make the determination.
780     * @return {@code true} if this attribute type is a sub-type of the provided
781     *         attribute type, or {@code false} if not.
782     * @throws NullPointerException
783     *             If {@code type} was {@code null}.
784     */
785    public boolean isSubTypeOf(final AttributeType type) {
786        AttributeType tmp = this;
787        do {
788            if (tmp.matches(type)) {
789                return true;
790            }
791            tmp = tmp.getSuperiorType();
792        } while (tmp != null);
793        return false;
794    }
795
796    /**
797     * Indicates whether or not this attribute type is a super-type of the
798     * provided attribute type.
799     *
800     * @param type
801     *            The attribute type for which to make the determination.
802     * @return {@code true} if this attribute type is a super-type of the
803     *         provided attribute type, or {@code false} if not.
804     * @throws NullPointerException
805     *             If {@code type} was {@code null}.
806     */
807    public boolean isSuperTypeOf(final AttributeType type) {
808        return type.isSubTypeOf(this);
809    }
810
811    /**
812     * Implements a place-holder tolerant version of {@link #equals}. This
813     * method returns {@code true} in the following cases:
814     * <ul>
815     * <li>this attribute type is equal to the provided attribute type as
816     * specified by {@link #equals}
817     * <li>this attribute type is a place-holder and the provided attribute type
818     * has a name which matches the name of this attribute type
819     * <li>the provided attribute type is a place-holder and this attribute type
820     * has a name which matches the name of the provided attribute type.
821     * </ul>
822     *
823     * @param type
824     *            The attribute type for which to make the determination.
825     * @return {@code true} if the provided attribute type matches this
826     *         attribute type.
827     */
828    public boolean matches(final AttributeType type) {
829        if (this == type) {
830            return true;
831        } else if (oid.equals(type.oid)) {
832            return true;
833        } else if (isPlaceHolder != type.isPlaceHolder) {
834            return isPlaceHolder ? type.hasName(normalizedName) : hasName(type.normalizedName);
835        } else {
836            return false;
837        }
838    }
839
840    @Override
841    void toStringContent(final StringBuilder buffer) {
842        buffer.append(oid);
843
844        if (!names.isEmpty()) {
845            final Iterator<String> iterator = names.iterator();
846
847            final String firstName = iterator.next();
848            if (iterator.hasNext()) {
849                buffer.append(" NAME ( '");
850                buffer.append(firstName);
851
852                while (iterator.hasNext()) {
853                    buffer.append("' '");
854                    buffer.append(iterator.next());
855                }
856
857                buffer.append("' )");
858            } else {
859                buffer.append(" NAME '");
860                buffer.append(firstName);
861                buffer.append("'");
862            }
863        }
864
865        appendDescription(buffer);
866
867        if (isObsolete) {
868            buffer.append(" OBSOLETE");
869        }
870
871        if (superiorTypeOID != null) {
872            buffer.append(" SUP ");
873            buffer.append(superiorTypeOID);
874        }
875
876        if (equalityMatchingRuleOID != null) {
877            buffer.append(" EQUALITY ");
878            buffer.append(equalityMatchingRuleOID);
879        }
880
881        if (orderingMatchingRuleOID != null) {
882            buffer.append(" ORDERING ");
883            buffer.append(orderingMatchingRuleOID);
884        }
885
886        if (substringMatchingRuleOID != null) {
887            buffer.append(" SUBSTR ");
888            buffer.append(substringMatchingRuleOID);
889        }
890
891        if (syntaxOID != null) {
892            buffer.append(" SYNTAX ");
893            buffer.append(syntaxOID);
894        }
895
896        if (isSingleValue()) {
897            buffer.append(" SINGLE-VALUE");
898        }
899
900        if (isCollective()) {
901            buffer.append(" COLLECTIVE");
902        }
903
904        if (isNoUserModification()) {
905            buffer.append(" NO-USER-MODIFICATION");
906        }
907
908        if (attributeUsage != null) {
909            buffer.append(" USAGE ");
910            buffer.append(attributeUsage);
911        }
912
913        if (approximateMatchingRuleOID != null) {
914            buffer.append(" ");
915            buffer.append(SCHEMA_PROPERTY_APPROX_RULE);
916            buffer.append(" '");
917            buffer.append(approximateMatchingRuleOID);
918            buffer.append("'");
919        }
920    }
921
922    boolean validate(final Schema schema, final List<AttributeType> invalidSchemaElements,
923            final List<LocalizableMessage> warnings) {
924        // Avoid validating this schema element more than once. This may occur
925        // if multiple attributes specify the same superior.
926        if (!needsValidating) {
927            return isValid;
928        }
929
930        // Prevent re-validation.
931        needsValidating = false;
932
933        if (superiorTypeOID != null) {
934            try {
935                superiorType = schema.getAttributeType(superiorTypeOID);
936            } catch (final UnknownSchemaElementException e) {
937                final LocalizableMessage message =
938                        WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_SUPERIOR_TYPE1.get(getNameOrOID(),
939                                superiorTypeOID);
940                failValidation(invalidSchemaElements, warnings, message);
941                return false;
942            }
943
944            // First ensure that the superior has been validated and fail if it
945            // is invalid.
946            if (!superiorType.validate(schema, invalidSchemaElements, warnings)) {
947                final LocalizableMessage message =
948                        WARN_ATTR_SYNTAX_ATTRTYPE_INVALID_SUPERIOR_TYPE.get(getNameOrOID(),
949                                superiorTypeOID);
950                failValidation(invalidSchemaElements, warnings, message);
951                return false;
952            }
953
954            // If there is a superior type, then it must have the same usage
955            // as the subordinate type. Also, if the superior type is
956            // collective, then so must the subordinate type be collective.
957            if (superiorType.getUsage() != getUsage()) {
958                final LocalizableMessage message =
959                        WARN_ATTR_SYNTAX_ATTRTYPE_INVALID_SUPERIOR_USAGE.get(getNameOrOID(),
960                                getUsage().toString(), superiorType.getNameOrOID());
961                failValidation(invalidSchemaElements, warnings, message);
962                return false;
963            }
964
965            if (superiorType.isCollective() != isCollective() && !isCollective()) {
966                LocalizableMessage message = WARN_ATTR_SYNTAX_ATTRTYPE_NONCOLLECTIVE_FROM_COLLECTIVE.get(
967                    getNameOrOID(), superiorType.getNameOrOID());
968                failValidation(invalidSchemaElements, warnings, message);
969                return false;
970            }
971        }
972
973        if (syntaxOID != null) {
974            if (!schema.hasSyntax(syntaxOID)) {
975                // Try substituting a syntax from the core schema. This will
976                // never fail since the core schema is non-strict and will
977                // substitute the syntax if required.
978                syntax = Schema.getCoreSchema().getSyntax(syntaxOID);
979                final LocalizableMessage message =
980                        WARN_ATTR_TYPE_NOT_DEFINED1.get(getNameOrOID(), syntaxOID, syntax.getOID());
981                warnings.add(message);
982            } else {
983                syntax = schema.getSyntax(syntaxOID);
984            }
985        } else if (getSuperiorType() != null && getSuperiorType().getSyntax() != null) {
986            // Try to inherit the syntax from the superior type if possible
987            syntax = getSuperiorType().getSyntax();
988        }
989
990        if (equalityMatchingRuleOID != null) {
991            // Use explicitly defined matching rule first.
992            try {
993                equalityMatchingRule = schema.getMatchingRule(equalityMatchingRuleOID);
994            } catch (final UnknownSchemaElementException e) {
995                final LocalizableMessage message =
996                        WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_EQUALITY_MR1.get(getNameOrOID(),
997                                equalityMatchingRuleOID);
998                failValidation(invalidSchemaElements, warnings, message);
999                return false;
1000            }
1001        } else if (getSuperiorType() != null && getSuperiorType().getEqualityMatchingRule() != null) {
1002            // Inherit matching rule from superior type if possible
1003            equalityMatchingRule = getSuperiorType().getEqualityMatchingRule();
1004        } else if (getSyntax() != null && getSyntax().getEqualityMatchingRule() != null) {
1005            // Use default for syntax
1006            equalityMatchingRule = getSyntax().getEqualityMatchingRule();
1007        }
1008
1009        if (orderingMatchingRuleOID != null) {
1010            // Use explicitly defined matching rule first.
1011            try {
1012                orderingMatchingRule = schema.getMatchingRule(orderingMatchingRuleOID);
1013            } catch (final UnknownSchemaElementException e) {
1014                final LocalizableMessage message =
1015                        WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_ORDERING_MR1.get(getNameOrOID(),
1016                                orderingMatchingRuleOID);
1017                failValidation(invalidSchemaElements, warnings, message);
1018                return false;
1019            }
1020        } else if (getSuperiorType() != null && getSuperiorType().getOrderingMatchingRule() != null) {
1021            // Inherit matching rule from superior type if possible
1022            orderingMatchingRule = getSuperiorType().getOrderingMatchingRule();
1023        } else if (getSyntax() != null && getSyntax().getOrderingMatchingRule() != null) {
1024            // Use default for syntax
1025            orderingMatchingRule = getSyntax().getOrderingMatchingRule();
1026        }
1027
1028        if (substringMatchingRuleOID != null) {
1029            // Use explicitly defined matching rule first.
1030            try {
1031                substringMatchingRule = schema.getMatchingRule(substringMatchingRuleOID);
1032            } catch (final UnknownSchemaElementException e) {
1033                final LocalizableMessage message =
1034                        WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_SUBSTRING_MR1.get(getNameOrOID(),
1035                                substringMatchingRuleOID);
1036                failValidation(invalidSchemaElements, warnings, message);
1037                return false;
1038            }
1039        } else if (getSuperiorType() != null
1040                && getSuperiorType().getSubstringMatchingRule() != null) {
1041            // Inherit matching rule from superior type if possible
1042            substringMatchingRule = getSuperiorType().getSubstringMatchingRule();
1043        } else if (getSyntax() != null && getSyntax().getSubstringMatchingRule() != null) {
1044            // Use default for syntax
1045            substringMatchingRule = getSyntax().getSubstringMatchingRule();
1046        }
1047
1048        if (approximateMatchingRuleOID != null) {
1049            // Use explicitly defined matching rule first.
1050            try {
1051                approximateMatchingRule = schema.getMatchingRule(approximateMatchingRuleOID);
1052            } catch (final UnknownSchemaElementException e) {
1053                final LocalizableMessage message =
1054                        WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_APPROXIMATE_MR1.get(getNameOrOID(),
1055                                approximateMatchingRuleOID);
1056                failValidation(invalidSchemaElements, warnings, message);
1057                return false;
1058            }
1059        } else if (getSuperiorType() != null
1060                && getSuperiorType().getApproximateMatchingRule() != null) {
1061            // Inherit matching rule from superior type if possible
1062            approximateMatchingRule = getSuperiorType().getApproximateMatchingRule();
1063        } else if (getSyntax() != null && getSyntax().getApproximateMatchingRule() != null) {
1064            // Use default for syntax
1065            approximateMatchingRule = getSyntax().getApproximateMatchingRule();
1066        }
1067
1068        // If the attribute type is COLLECTIVE, then it must have a usage of
1069        // userApplications.
1070        if (isCollective() && getUsage() != AttributeUsage.USER_APPLICATIONS) {
1071            final LocalizableMessage message =
1072                    WARN_ATTR_SYNTAX_ATTRTYPE_COLLECTIVE_IS_OPERATIONAL.get(getNameOrOID());
1073            warnings.add(message);
1074        }
1075
1076        // If the attribute type is NO-USER-MODIFICATION, then it must not
1077        // have a usage of userApplications.
1078        if (isNoUserModification() && getUsage() == AttributeUsage.USER_APPLICATIONS) {
1079            final LocalizableMessage message =
1080                    WARN_ATTR_SYNTAX_ATTRTYPE_NO_USER_MOD_NOT_OPERATIONAL.get(getNameOrOID());
1081            warnings.add(message);
1082        }
1083
1084        return isValid = true;
1085    }
1086
1087    private void failValidation(final List<AttributeType> invalidSchemaElements,
1088            final List<LocalizableMessage> warnings, final LocalizableMessage message) {
1089        invalidSchemaElements.add(this);
1090        warnings.add(ERR_ATTR_TYPE_VALIDATION_FAIL.get(toString(), message));
1091    }
1092}