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