001/*
002 * Copyright 2007-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-2020 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2008-2020 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk.schema;
037
038
039
040import java.io.File;
041import java.io.IOException;
042import java.io.InputStream;
043import java.io.Serializable;
044import java.util.ArrayList;
045import java.util.Arrays;
046import java.util.Collections;
047import java.util.LinkedHashMap;
048import java.util.LinkedHashSet;
049import java.util.List;
050import java.util.Map;
051import java.util.Set;
052import java.util.concurrent.atomic.AtomicReference;
053
054import com.unboundid.ldap.sdk.Attribute;
055import com.unboundid.ldap.sdk.Entry;
056import com.unboundid.ldap.sdk.Filter;
057import com.unboundid.ldap.sdk.LDAPConnection;
058import com.unboundid.ldap.sdk.LDAPException;
059import com.unboundid.ldap.sdk.ReadOnlyEntry;
060import com.unboundid.ldap.sdk.ResultCode;
061import com.unboundid.ldap.sdk.SearchScope;
062import com.unboundid.ldif.LDIFException;
063import com.unboundid.ldif.LDIFReader;
064import com.unboundid.util.Debug;
065import com.unboundid.util.NotMutable;
066import com.unboundid.util.StaticUtils;
067import com.unboundid.util.ThreadSafety;
068import com.unboundid.util.ThreadSafetyLevel;
069import com.unboundid.util.Validator;
070
071import static com.unboundid.ldap.sdk.schema.SchemaMessages.*;
072
073
074
075/**
076 * This class provides a data structure for representing a directory server
077 * subschema subentry.  This includes information about the attribute syntaxes,
078 * matching rules, attribute types, object classes, name forms, DIT content
079 * rules, DIT structure rules, and matching rule uses defined in the server
080 * schema.
081 */
082@NotMutable()
083@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
084public final class Schema
085       implements Serializable
086{
087  /**
088   * The name of the attribute used to hold the attribute syntax definitions.
089   */
090  public static final String ATTR_ATTRIBUTE_SYNTAX = "ldapSyntaxes";
091
092
093
094  /**
095   * The name of the attribute used to hold the attribute type definitions.
096   */
097  public static final String ATTR_ATTRIBUTE_TYPE = "attributeTypes";
098
099
100
101  /**
102   * The name of the attribute used to hold the DIT content rule definitions.
103   */
104  public static final String ATTR_DIT_CONTENT_RULE = "dITContentRules";
105
106
107
108  /**
109   * The name of the attribute used to hold the DIT structure rule definitions.
110   */
111  public static final String ATTR_DIT_STRUCTURE_RULE = "dITStructureRules";
112
113
114
115  /**
116   * The name of the attribute used to hold the matching rule definitions.
117   */
118  public static final String ATTR_MATCHING_RULE = "matchingRules";
119
120
121
122  /**
123   * The name of the attribute used to hold the matching rule use definitions.
124   */
125  public static final String ATTR_MATCHING_RULE_USE = "matchingRuleUse";
126
127
128
129  /**
130   * The name of the attribute used to hold the name form definitions.
131   */
132  public static final String ATTR_NAME_FORM = "nameForms";
133
134
135
136  /**
137   * The name of the attribute used to hold the object class definitions.
138   */
139  public static final String ATTR_OBJECT_CLASS = "objectClasses";
140
141
142
143  /**
144   * The name of the attribute used to hold the DN of the subschema subentry
145   * with the schema information that governs a specified entry.
146   */
147  public static final String ATTR_SUBSCHEMA_SUBENTRY = "subschemaSubentry";
148
149
150
151  /**
152   * The default standard schema available for use in the LDAP SDK.
153   */
154  private static final AtomicReference<Schema> DEFAULT_STANDARD_SCHEMA =
155       new AtomicReference<>();
156
157
158
159  /**
160   * The set of request attributes that will be used when retrieving the server
161   * subschema subentry in order to retrieve all of the schema elements.
162   */
163  private static final String[] SCHEMA_REQUEST_ATTRS =
164  {
165    "*",
166    ATTR_ATTRIBUTE_SYNTAX,
167    ATTR_ATTRIBUTE_TYPE,
168    ATTR_DIT_CONTENT_RULE,
169    ATTR_DIT_STRUCTURE_RULE,
170    ATTR_MATCHING_RULE,
171    ATTR_MATCHING_RULE_USE,
172    ATTR_NAME_FORM,
173    ATTR_OBJECT_CLASS
174  };
175
176
177
178  /**
179   * The set of request attributes that will be used when retrieving the
180   * subschema subentry attribute from a specified entry in order to determine
181   * the location of the server schema definitions.
182   */
183  private static final String[] SUBSCHEMA_SUBENTRY_REQUEST_ATTRS =
184  {
185    ATTR_SUBSCHEMA_SUBENTRY
186  };
187
188
189
190  /**
191   * Retrieves the resource path that may be used to obtain a file with a number
192   * of standard schema definitions.
193   */
194  private static final String DEFAULT_SCHEMA_RESOURCE_PATH =
195       "com/unboundid/ldap/sdk/schema/standard-schema.ldif";
196
197
198
199  /**
200   * The serial version UID for this serializable class.
201   */
202  private static final long serialVersionUID = 8081839633831517925L;
203
204
205
206  // A map of all subordinate attribute type definitions for each attribute
207  // type definition.
208  private final Map<AttributeTypeDefinition,List<AttributeTypeDefinition>>
209       subordinateAttributeTypes;
210
211  // The set of attribute syntaxes mapped from lowercase name/OID to syntax.
212  private final Map<String,AttributeSyntaxDefinition> asMap;
213
214  // The set of attribute types mapped from lowercase name/OID to type.
215  private final Map<String,AttributeTypeDefinition> atMap;
216
217  // The set of DIT content rules mapped from lowercase name/OID to rule.
218  private final Map<String,DITContentRuleDefinition> dcrMap;
219
220  // The set of DIT structure rules mapped from rule ID to rule.
221  private final Map<Integer,DITStructureRuleDefinition> dsrMapByID;
222
223  // The set of DIT structure rules mapped from lowercase name to rule.
224  private final Map<String,DITStructureRuleDefinition> dsrMapByName;
225
226  // The set of DIT structure rules mapped from lowercase name to rule.
227  private final Map<String,DITStructureRuleDefinition> dsrMapByNameForm;
228
229  // The set of matching rules mapped from lowercase name/OID to rule.
230  private final Map<String,MatchingRuleDefinition> mrMap;
231
232  // The set of matching rule uses mapped from matching rule OID to use.
233  private final Map<String,MatchingRuleUseDefinition> mruMap;
234
235  // The set of name forms mapped from lowercase name/OID to name form.
236  private final Map<String,NameFormDefinition> nfMapByName;
237
238  // The set of name forms mapped from structural class OID to name form.
239  private final Map<String,NameFormDefinition> nfMapByOC;
240
241  // The set of object classes mapped from lowercase name/OID to class.
242  private final Map<String,ObjectClassDefinition> ocMap;
243
244  // The entry used to create this schema object.
245  private final ReadOnlyEntry schemaEntry;
246
247  // The set of attribute syntaxes defined in the schema.
248  private final Set<AttributeSyntaxDefinition> asSet;
249
250  // The set of attribute types defined in the schema.
251  private final Set<AttributeTypeDefinition> atSet;
252
253  // The set of operational attribute types defined in the schema.
254  private final Set<AttributeTypeDefinition> operationalATSet;
255
256  // The set of user attribute types defined in the schema.
257  private final Set<AttributeTypeDefinition> userATSet;
258
259  // The set of DIT content rules defined in the schema.
260  private final Set<DITContentRuleDefinition> dcrSet;
261
262  // The set of DIT structure rules defined in the schema.
263  private final Set<DITStructureRuleDefinition> dsrSet;
264
265  // The set of matching rules defined in the schema.
266  private final Set<MatchingRuleDefinition> mrSet;
267
268  // The set of matching rule uses defined in the schema.
269  private final Set<MatchingRuleUseDefinition> mruSet;
270
271  // The set of name forms defined in the schema.
272  private final Set<NameFormDefinition> nfSet;
273
274  // The set of object classes defined in the schema.
275  private final Set<ObjectClassDefinition> ocSet;
276
277  // The set of abstract object classes defined in the schema.
278  private final Set<ObjectClassDefinition> abstractOCSet;
279
280  // The set of auxiliary object classes defined in the schema.
281  private final Set<ObjectClassDefinition> auxiliaryOCSet;
282
283  // The set of structural object classes defined in the schema.
284  private final Set<ObjectClassDefinition> structuralOCSet;
285
286
287
288  /**
289   * Creates a new schema object by decoding the information in the provided
290   * entry.  Any schema elements that cannot be parsed will be silently ignored.
291   *
292   * @param  schemaEntry  The schema entry to decode.  It must not be
293   *                      {@code null}.
294   */
295  public Schema(final Entry schemaEntry)
296  {
297    this(schemaEntry, null, null, null, null, null, null, null, null);
298  }
299
300
301
302  /**
303   * Creates a new schema object by decoding the information in the provided
304   * entry, optionally capturing any information about unparsable values in the
305   * provided maps.
306   *
307   * @param  schemaEntry                  The schema entry to decode.  It must
308   *                                      not be {@code null}.
309   * @param  unparsableAttributeSyntaxes  A map that will be updated with with
310   *                                      information about any attribute syntax
311   *                                      definitions that cannot be parsed.  It
312   *                                      may be {@code null} if unparsable
313   *                                      attribute syntax definitions should be
314   *                                      silently ignored.
315   * @param  unparsableMatchingRules      A map that will be updated with with
316   *                                      information about any matching rule
317   *                                      definitions that cannot be parsed.  It
318   *                                      may be {@code null} if unparsable
319   *                                      attribute syntax definitions should be
320   *                                      silently ignored.
321   * @param  unparsableAttributeTypes     A map that will be updated with with
322   *                                      information about any attribute type
323   *                                      definitions that cannot be parsed.  It
324   *                                      may be {@code null} if unparsable
325   *                                      attribute syntax definitions should be
326   *                                      silently ignored.
327   * @param  unparsableObjectClasses      A map that will be updated with with
328   *                                      information about any object class
329   *                                      definitions that cannot be parsed.  It
330   *                                      may be {@code null} if unparsable
331   *                                      attribute syntax definitions should be
332   *                                      silently ignored.
333   * @param  unparsableDITContentRules    A map that will be updated with with
334   *                                      information about any DIT content rule
335   *                                      definitions that cannot be parsed.  It
336   *                                      may be {@code null} if unparsable
337   *                                      attribute syntax definitions should be
338   *                                      silently ignored.
339   * @param  unparsableDITStructureRules  A map that will be updated with with
340   *                                      information about any DIT structure
341   *                                      rule definitions that cannot be
342   *                                      parsed.  It may be {@code null} if
343   *                                      unparsable attribute syntax
344   *                                      definitions should be silently
345   *                                      ignored.
346   * @param  unparsableNameForms          A map that will be updated with with
347   *                                      information about any name form
348   *                                      definitions that cannot be parsed.  It
349   *                                      may be {@code null} if unparsable
350   *                                      attribute syntax definitions should be
351   *                                      silently ignored.
352   * @param  unparsableMatchingRuleUses   A map that will be updated with with
353   *                                      information about any matching rule
354   *                                      use definitions that cannot be parsed.
355   *                                      It may be {@code null} if unparsable
356   *                                      attribute syntax definitions should be
357   *                                      silently ignored.
358   */
359  public Schema(final Entry schemaEntry,
360                final Map<String,LDAPException> unparsableAttributeSyntaxes,
361                final Map<String,LDAPException> unparsableMatchingRules,
362                final Map<String,LDAPException> unparsableAttributeTypes,
363                final Map<String,LDAPException> unparsableObjectClasses,
364                final Map<String,LDAPException> unparsableDITContentRules,
365                final Map<String,LDAPException> unparsableDITStructureRules,
366                final Map<String,LDAPException> unparsableNameForms,
367                final Map<String,LDAPException> unparsableMatchingRuleUses)
368  {
369    this.schemaEntry = new ReadOnlyEntry(schemaEntry);
370
371    // Decode the attribute syntaxes from the schema entry.
372    String[] defs = schemaEntry.getAttributeValues(ATTR_ATTRIBUTE_SYNTAX);
373    if (defs == null)
374    {
375      asMap = Collections.emptyMap();
376      asSet = Collections.emptySet();
377    }
378    else
379    {
380      final LinkedHashMap<String,AttributeSyntaxDefinition> m =
381           new LinkedHashMap<>(StaticUtils.computeMapCapacity(defs.length));
382      final LinkedHashSet<AttributeSyntaxDefinition> s =
383           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
384
385      for (final String def : defs)
386      {
387        try
388        {
389          final AttributeSyntaxDefinition as =
390               new AttributeSyntaxDefinition(def);
391          s.add(as);
392          m.put(StaticUtils.toLowerCase(as.getOID()), as);
393        }
394        catch (final LDAPException le)
395        {
396          Debug.debugException(le);
397          if (unparsableAttributeSyntaxes != null)
398          {
399            unparsableAttributeSyntaxes.put(def, le);
400          }
401        }
402      }
403
404      asMap = Collections.unmodifiableMap(m);
405      asSet = Collections.unmodifiableSet(s);
406    }
407
408
409    // Decode the attribute types from the schema entry.
410    defs = schemaEntry.getAttributeValues(ATTR_ATTRIBUTE_TYPE);
411    if (defs == null)
412    {
413      atMap            = Collections.emptyMap();
414      atSet            = Collections.emptySet();
415      operationalATSet = Collections.emptySet();
416      userATSet        = Collections.emptySet();
417    }
418    else
419    {
420      final LinkedHashMap<String,AttributeTypeDefinition> m =
421           new LinkedHashMap<>(StaticUtils.computeMapCapacity(2*defs.length));
422      final LinkedHashSet<AttributeTypeDefinition> s =
423           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
424      final LinkedHashSet<AttributeTypeDefinition> sUser =
425           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
426      final LinkedHashSet<AttributeTypeDefinition> sOperational =
427           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
428
429      for (final String def : defs)
430      {
431        try
432        {
433          final AttributeTypeDefinition at = new AttributeTypeDefinition(def);
434          s.add(at);
435          m.put(StaticUtils.toLowerCase(at.getOID()), at);
436          for (final String name : at.getNames())
437          {
438            m.put(StaticUtils.toLowerCase(name), at);
439          }
440
441          if (at.isOperational())
442          {
443            sOperational.add(at);
444          }
445          else
446          {
447            sUser.add(at);
448          }
449        }
450        catch (final LDAPException le)
451        {
452          Debug.debugException(le);
453          if (unparsableAttributeTypes != null)
454          {
455            unparsableAttributeTypes.put(def, le);
456          }
457        }
458      }
459
460      atMap            = Collections.unmodifiableMap(m);
461      atSet            = Collections.unmodifiableSet(s);
462      operationalATSet = Collections.unmodifiableSet(sOperational);
463      userATSet        = Collections.unmodifiableSet(sUser);
464    }
465
466
467    // Decode the DIT content rules from the schema entry.
468    defs = schemaEntry.getAttributeValues(ATTR_DIT_CONTENT_RULE);
469    if (defs == null)
470    {
471      dcrMap = Collections.emptyMap();
472      dcrSet = Collections.emptySet();
473    }
474    else
475    {
476      final LinkedHashMap<String,DITContentRuleDefinition> m =
477           new LinkedHashMap<>(2*defs.length);
478      final LinkedHashSet<DITContentRuleDefinition> s =
479           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
480
481      for (final String def : defs)
482      {
483        try
484        {
485          final DITContentRuleDefinition dcr =
486               new DITContentRuleDefinition(def);
487          s.add(dcr);
488          m.put(StaticUtils.toLowerCase(dcr.getOID()), dcr);
489          for (final String name : dcr.getNames())
490          {
491            m.put(StaticUtils.toLowerCase(name), dcr);
492          }
493        }
494        catch (final LDAPException le)
495        {
496          Debug.debugException(le);
497          if (unparsableDITContentRules != null)
498          {
499            unparsableDITContentRules.put(def, le);
500          }
501        }
502      }
503
504      dcrMap = Collections.unmodifiableMap(m);
505      dcrSet = Collections.unmodifiableSet(s);
506    }
507
508
509    // Decode the DIT structure rules from the schema entry.
510    defs = schemaEntry.getAttributeValues(ATTR_DIT_STRUCTURE_RULE);
511    if (defs == null)
512    {
513      dsrMapByID       = Collections.emptyMap();
514      dsrMapByName     = Collections.emptyMap();
515      dsrMapByNameForm = Collections.emptyMap();
516      dsrSet           = Collections.emptySet();
517    }
518    else
519    {
520      final LinkedHashMap<Integer,DITStructureRuleDefinition> mID =
521           new LinkedHashMap<>(StaticUtils.computeMapCapacity(defs.length));
522      final LinkedHashMap<String,DITStructureRuleDefinition> mN =
523           new LinkedHashMap<>(StaticUtils.computeMapCapacity(defs.length));
524      final LinkedHashMap<String,DITStructureRuleDefinition> mNF =
525           new LinkedHashMap<>(StaticUtils.computeMapCapacity(defs.length));
526      final LinkedHashSet<DITStructureRuleDefinition> s =
527           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
528
529      for (final String def : defs)
530      {
531        try
532        {
533          final DITStructureRuleDefinition dsr =
534               new DITStructureRuleDefinition(def);
535          s.add(dsr);
536          mID.put(dsr.getRuleID(), dsr);
537          mNF.put(StaticUtils.toLowerCase(dsr.getNameFormID()), dsr);
538          for (final String name : dsr.getNames())
539          {
540            mN.put(StaticUtils.toLowerCase(name), dsr);
541          }
542        }
543        catch (final LDAPException le)
544        {
545          Debug.debugException(le);
546          if (unparsableDITStructureRules != null)
547          {
548            unparsableDITStructureRules.put(def, le);
549          }
550        }
551      }
552
553      dsrMapByID       = Collections.unmodifiableMap(mID);
554      dsrMapByName     = Collections.unmodifiableMap(mN);
555      dsrMapByNameForm = Collections.unmodifiableMap(mNF);
556      dsrSet           = Collections.unmodifiableSet(s);
557    }
558
559
560    // Decode the matching rules from the schema entry.
561    defs = schemaEntry.getAttributeValues(ATTR_MATCHING_RULE);
562    if (defs == null)
563    {
564      mrMap = Collections.emptyMap();
565      mrSet = Collections.emptySet();
566    }
567    else
568    {
569      final LinkedHashMap<String,MatchingRuleDefinition> m =
570           new LinkedHashMap<>(StaticUtils.computeMapCapacity(2*defs.length));
571      final LinkedHashSet<MatchingRuleDefinition> s =
572           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
573
574      for (final String def : defs)
575      {
576        try
577        {
578          final MatchingRuleDefinition mr = new MatchingRuleDefinition(def);
579          s.add(mr);
580          m.put(StaticUtils.toLowerCase(mr.getOID()), mr);
581          for (final String name : mr.getNames())
582          {
583            m.put(StaticUtils.toLowerCase(name), mr);
584          }
585        }
586        catch (final LDAPException le)
587        {
588          Debug.debugException(le);
589          if (unparsableMatchingRules != null)
590          {
591            unparsableMatchingRules.put(def, le);
592          }
593        }
594      }
595
596      mrMap = Collections.unmodifiableMap(m);
597      mrSet = Collections.unmodifiableSet(s);
598    }
599
600
601    // Decode the matching rule uses from the schema entry.
602    defs = schemaEntry.getAttributeValues(ATTR_MATCHING_RULE_USE);
603    if (defs == null)
604    {
605      mruMap = Collections.emptyMap();
606      mruSet = Collections.emptySet();
607    }
608    else
609    {
610      final LinkedHashMap<String,MatchingRuleUseDefinition> m =
611           new LinkedHashMap<>(StaticUtils.computeMapCapacity(2*defs.length));
612      final LinkedHashSet<MatchingRuleUseDefinition> s =
613           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
614
615      for (final String def : defs)
616      {
617        try
618        {
619          final MatchingRuleUseDefinition mru =
620               new MatchingRuleUseDefinition(def);
621          s.add(mru);
622          m.put(StaticUtils.toLowerCase(mru.getOID()), mru);
623          for (final String name : mru.getNames())
624          {
625            m.put(StaticUtils.toLowerCase(name), mru);
626          }
627        }
628        catch (final LDAPException le)
629        {
630          Debug.debugException(le);
631          if (unparsableMatchingRuleUses != null)
632          {
633            unparsableMatchingRuleUses.put(def, le);
634          }
635        }
636      }
637
638      mruMap = Collections.unmodifiableMap(m);
639      mruSet = Collections.unmodifiableSet(s);
640    }
641
642
643    // Decode the name forms from the schema entry.
644    defs = schemaEntry.getAttributeValues(ATTR_NAME_FORM);
645    if (defs == null)
646    {
647      nfMapByName = Collections.emptyMap();
648      nfMapByOC   = Collections.emptyMap();
649      nfSet       = Collections.emptySet();
650    }
651    else
652    {
653      final LinkedHashMap<String,NameFormDefinition> mN =
654           new LinkedHashMap<>(StaticUtils.computeMapCapacity(2*defs.length));
655      final LinkedHashMap<String,NameFormDefinition> mOC =
656           new LinkedHashMap<>(StaticUtils.computeMapCapacity(defs.length));
657      final LinkedHashSet<NameFormDefinition> s =
658           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
659
660      for (final String def : defs)
661      {
662        try
663        {
664          final NameFormDefinition nf = new NameFormDefinition(def);
665          s.add(nf);
666          mOC.put(StaticUtils.toLowerCase(nf.getStructuralClass()), nf);
667          mN.put(StaticUtils.toLowerCase(nf.getOID()), nf);
668          for (final String name : nf.getNames())
669          {
670            mN.put(StaticUtils.toLowerCase(name), nf);
671          }
672        }
673        catch (final LDAPException le)
674        {
675          Debug.debugException(le);
676          if(unparsableNameForms != null)
677          {
678            unparsableNameForms.put(def, le);
679          }
680        }
681      }
682
683      nfMapByName = Collections.unmodifiableMap(mN);
684      nfMapByOC   = Collections.unmodifiableMap(mOC);
685      nfSet       = Collections.unmodifiableSet(s);
686    }
687
688
689    // Decode the object classes from the schema entry.
690    defs = schemaEntry.getAttributeValues(ATTR_OBJECT_CLASS);
691    if (defs == null)
692    {
693      ocMap           = Collections.emptyMap();
694      ocSet           = Collections.emptySet();
695      abstractOCSet   = Collections.emptySet();
696      auxiliaryOCSet  = Collections.emptySet();
697      structuralOCSet = Collections.emptySet();
698    }
699    else
700    {
701      final LinkedHashMap<String,ObjectClassDefinition> m =
702           new LinkedHashMap<>(StaticUtils.computeMapCapacity(2*defs.length));
703      final LinkedHashSet<ObjectClassDefinition> s =
704           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
705      final LinkedHashSet<ObjectClassDefinition> sAbstract =
706           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
707      final LinkedHashSet<ObjectClassDefinition> sAuxiliary =
708           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
709      final LinkedHashSet<ObjectClassDefinition> sStructural =
710           new LinkedHashSet<>(StaticUtils.computeMapCapacity(defs.length));
711
712      for (final String def : defs)
713      {
714        try
715        {
716          final ObjectClassDefinition oc = new ObjectClassDefinition(def);
717          s.add(oc);
718          m.put(StaticUtils.toLowerCase(oc.getOID()), oc);
719          for (final String name : oc.getNames())
720          {
721            m.put(StaticUtils.toLowerCase(name), oc);
722          }
723
724          switch (oc.getObjectClassType(null))
725          {
726            case ABSTRACT:
727              sAbstract.add(oc);
728              break;
729            case AUXILIARY:
730              sAuxiliary.add(oc);
731              break;
732            case STRUCTURAL:
733              sStructural.add(oc);
734              break;
735          }
736        }
737        catch (final LDAPException le)
738        {
739          Debug.debugException(le);
740          if (unparsableObjectClasses != null)
741          {
742            unparsableObjectClasses.put(def, le);
743          }
744        }
745      }
746
747      ocMap           = Collections.unmodifiableMap(m);
748      ocSet           = Collections.unmodifiableSet(s);
749      abstractOCSet   = Collections.unmodifiableSet(sAbstract);
750      auxiliaryOCSet  = Collections.unmodifiableSet(sAuxiliary);
751      structuralOCSet = Collections.unmodifiableSet(sStructural);
752    }
753
754
755    // Populate the map of subordinate attribute types.
756    final LinkedHashMap<AttributeTypeDefinition,List<AttributeTypeDefinition>>
757         subAttrTypes = new LinkedHashMap<>(
758              StaticUtils.computeMapCapacity(atSet.size()));
759    for (final AttributeTypeDefinition d : atSet)
760    {
761      AttributeTypeDefinition sup = d.getSuperiorType(this);
762      while (sup != null)
763      {
764        List<AttributeTypeDefinition> l = subAttrTypes.get(sup);
765        if (l == null)
766        {
767          l = new ArrayList<>(1);
768          subAttrTypes.put(sup, l);
769        }
770        l.add(d);
771
772        sup = sup.getSuperiorType(this);
773      }
774    }
775    subordinateAttributeTypes = Collections.unmodifiableMap(subAttrTypes);
776  }
777
778
779
780  /**
781   * Parses all schema elements contained in the provided entry.  This method
782   * differs from the {@link #Schema(Entry)} constructor in that this method
783   * will throw an exception if it encounters any unparsable schema elements,
784   * while the constructor will silently ignore them.  Alternately, the
785   * 'constructor that takes a bunch of maps can be used to
786   *
787   * @param  schemaEntry  The schema entry to parse.  It must not be
788   *                      {@code null}.
789   *
790   * @return  The schema entry that was parsed.
791   *
792   * @throws  LDAPException  If the provided entry contains any schema element
793   *                         definitions that cannot be parsed.
794   */
795  public static Schema parseSchemaEntry(final Entry schemaEntry)
796         throws LDAPException
797  {
798    final Map<String,LDAPException> unparsableAttributeSyntaxes =
799         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
800    final Map<String,LDAPException> unparsableMatchingRules =
801         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
802    final Map<String,LDAPException> unparsableAttributeTypes =
803         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
804    final Map<String,LDAPException> unparsableObjectClasses =
805         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
806    final Map<String,LDAPException> unparsableDITContentRules =
807         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
808    final Map<String,LDAPException> unparsableDITStructureRules =
809         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
810    final Map<String,LDAPException> unparsableNameForms =
811         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
812    final Map<String,LDAPException> unparsableMatchingRuleUses =
813         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
814
815    final Schema schema = new Schema(schemaEntry, unparsableAttributeSyntaxes,
816         unparsableMatchingRules, unparsableAttributeTypes,
817         unparsableObjectClasses, unparsableDITContentRules,
818         unparsableDITStructureRules, unparsableNameForms,
819         unparsableMatchingRuleUses);
820    if (unparsableAttributeSyntaxes.isEmpty() &&
821         unparsableMatchingRules.isEmpty() &&
822         unparsableAttributeTypes.isEmpty() &&
823         unparsableObjectClasses.isEmpty() &&
824         unparsableDITContentRules.isEmpty() &&
825         unparsableDITStructureRules.isEmpty() &&
826         unparsableNameForms.isEmpty() &&
827         unparsableMatchingRuleUses.isEmpty())
828    {
829      return schema;
830    }
831
832    final StringBuilder messageBuffer = new StringBuilder();
833    for (final Map.Entry<String,LDAPException> e :
834         unparsableAttributeSyntaxes.entrySet())
835    {
836      appendErrorMessage(messageBuffer,
837           ERR_SCHEMA_UNPARSABLE_AS.get(ATTR_ATTRIBUTE_SYNTAX, e.getKey(),
838                StaticUtils.getExceptionMessage(e.getValue())));
839    }
840
841    for (final Map.Entry<String,LDAPException> e :
842         unparsableMatchingRules.entrySet())
843    {
844      appendErrorMessage(messageBuffer,
845           ERR_SCHEMA_UNPARSABLE_MR.get(ATTR_MATCHING_RULE, e.getKey(),
846                StaticUtils.getExceptionMessage(e.getValue())));
847    }
848
849    for (final Map.Entry<String,LDAPException> e :
850         unparsableAttributeTypes.entrySet())
851    {
852      appendErrorMessage(messageBuffer,
853           ERR_SCHEMA_UNPARSABLE_AT.get(ATTR_ATTRIBUTE_TYPE, e.getKey(),
854                StaticUtils.getExceptionMessage(e.getValue())));
855    }
856
857    for (final Map.Entry<String,LDAPException> e :
858         unparsableObjectClasses.entrySet())
859    {
860      appendErrorMessage(messageBuffer,
861           ERR_SCHEMA_UNPARSABLE_OC.get(ATTR_OBJECT_CLASS, e.getKey(),
862                StaticUtils.getExceptionMessage(e.getValue())));
863    }
864
865    for (final Map.Entry<String,LDAPException> e :
866         unparsableDITContentRules.entrySet())
867    {
868      appendErrorMessage(messageBuffer,
869           ERR_SCHEMA_UNPARSABLE_DCR.get(ATTR_DIT_CONTENT_RULE, e.getKey(),
870                StaticUtils.getExceptionMessage(e.getValue())));
871    }
872
873    for (final Map.Entry<String,LDAPException> e :
874         unparsableDITStructureRules.entrySet())
875    {
876      appendErrorMessage(messageBuffer,
877           ERR_SCHEMA_UNPARSABLE_DSR.get(ATTR_DIT_STRUCTURE_RULE, e.getKey(),
878                StaticUtils.getExceptionMessage(e.getValue())));
879    }
880
881    for (final Map.Entry<String,LDAPException> e :
882         unparsableNameForms.entrySet())
883    {
884      appendErrorMessage(messageBuffer,
885           ERR_SCHEMA_UNPARSABLE_NF.get(ATTR_NAME_FORM, e.getKey(),
886                StaticUtils.getExceptionMessage(e.getValue())));
887    }
888
889    for (final Map.Entry<String,LDAPException> e :
890         unparsableMatchingRuleUses.entrySet())
891    {
892      appendErrorMessage(messageBuffer,
893           ERR_SCHEMA_UNPARSABLE_MRU.get(ATTR_MATCHING_RULE_USE, e.getKey(),
894                StaticUtils.getExceptionMessage(e.getValue())));
895    }
896
897    throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
898         messageBuffer.toString());
899  }
900
901
902
903  /**
904   * Appends the provided message to the given buffer, adding spaces and
905   * punctuation if necessary.
906   *
907   * @param  buffer   The buffer to which the message should be appended.
908   * @param  message  The message to append to the buffer.
909   */
910  private static void appendErrorMessage(final StringBuilder buffer,
911                                         final String message)
912  {
913    final int length = buffer.length();
914    if (length > 0)
915    {
916      if (buffer.charAt(length - 1) == '.')
917      {
918        buffer.append("  ");
919      }
920      else
921      {
922        buffer.append(".  ");
923      }
924    }
925
926    buffer.append(message);
927  }
928
929
930
931  /**
932   * Retrieves the directory server schema over the provided connection.  The
933   * root DSE will first be retrieved in order to get its subschemaSubentry DN,
934   * and then that entry will be retrieved from the server and its contents
935   * decoded as schema elements.  This should be sufficient for directories that
936   * only provide a single schema, but for directories with multiple schemas it
937   * may be necessary to specify the DN of an entry for which to retrieve the
938   * subschema subentry.  Any unparsable schema elements will be silently
939   * ignored.
940   *
941   * @param  connection  The connection to use in order to retrieve the server
942   *                     schema.  It must not be {@code null}.
943   *
944   * @return  A decoded representation of the server schema.
945   *
946   * @throws  LDAPException  If a problem occurs while obtaining the server
947   *                         schema.
948   */
949  public static Schema getSchema(final LDAPConnection connection)
950         throws LDAPException
951  {
952    return getSchema(connection, "");
953  }
954
955
956
957  /**
958   * Retrieves the directory server schema that governs the specified entry.
959   * In some servers, different portions of the DIT may be served by different
960   * schemas, and in such cases it will be necessary to provide the DN of the
961   * target entry in order to ensure that the appropriate schema which governs
962   * that entry is returned.  For servers that support only a single schema,
963   * any entry DN (including that of the root DSE) should be sufficient.  Any
964   * unparsable schema elements will be silently ignored.
965   *
966   * @param  connection  The connection to use in order to retrieve the server
967   *                     schema.  It must not be {@code null}.
968   * @param  entryDN     The DN of the entry for which to retrieve the governing
969   *                     schema.  It may be {@code null} or an empty string in
970   *                     order to retrieve the schema that governs the server's
971   *                     root DSE.
972   *
973   * @return  A decoded representation of the server schema, or {@code null} if
974   *          it is not available for some reason (e.g., the client does not
975   *          have permission to read the server schema).
976   *
977   * @throws  LDAPException  If a problem occurs while obtaining the server
978   *                         schema.
979   */
980  public static Schema getSchema(final LDAPConnection connection,
981                                 final String entryDN)
982         throws LDAPException
983  {
984    return getSchema(connection, entryDN, false);
985  }
986
987
988
989  /**
990   * Retrieves the directory server schema that governs the specified entry.
991   * In some servers, different portions of the DIT may be served by different
992   * schemas, and in such cases it will be necessary to provide the DN of the
993   * target entry in order to ensure that the appropriate schema which governs
994   * that entry is returned.  For servers that support only a single schema,
995   * any entry DN (including that of the root DSE) should be sufficient.  This
996   * method may optionally throw an exception if the retrieved schema contains
997   * one or more unparsable schema elements.
998   *
999   * @param  connection                The connection to use in order to
1000   *                                   retrieve the server schema.  It must not
1001   *                                   be {@code null}.
1002   * @param  entryDN                   The DN of the entry for which to retrieve
1003   *                                   the governing schema.  It may be
1004   *                                   {@code null} or an empty string in order
1005   *                                   to retrieve the schema that governs the
1006   *                                   server's root DSE.
1007   * @param  throwOnUnparsableElement  Indicates whether to throw an exception
1008   *                                   if the schema entry that is retrieved has
1009   *                                   one or more unparsable schema elements.
1010   *
1011   * @return  A decoded representation of the server schema, or {@code null} if
1012   *          it is not available for some reason (e.g., the client does not
1013   *          have permission to read the server schema).
1014   *
1015   * @throws  LDAPException  If a problem occurs while obtaining the server
1016   *                         schema, or if the schema contains one or more
1017   *                         unparsable elements and
1018   *                         {@code throwOnUnparsableElement} is {@code true}.
1019   */
1020  public static Schema getSchema(final LDAPConnection connection,
1021                                 final String entryDN,
1022                                 final boolean throwOnUnparsableElement)
1023         throws LDAPException
1024  {
1025    Validator.ensureNotNull(connection);
1026
1027    final String subschemaSubentryDN;
1028    if (entryDN == null)
1029    {
1030      subschemaSubentryDN = getSubschemaSubentryDN(connection, "");
1031    }
1032    else
1033    {
1034      subschemaSubentryDN = getSubschemaSubentryDN(connection, entryDN);
1035    }
1036
1037    if (subschemaSubentryDN == null)
1038    {
1039      return null;
1040    }
1041
1042    final Entry schemaEntry = connection.searchForEntry(subschemaSubentryDN,
1043         SearchScope.BASE,
1044         Filter.createEqualityFilter("objectClass", "subschema"),
1045         SCHEMA_REQUEST_ATTRS);
1046    if (schemaEntry == null)
1047    {
1048      return null;
1049    }
1050
1051    if (throwOnUnparsableElement)
1052    {
1053      return parseSchemaEntry(schemaEntry);
1054    }
1055    else
1056    {
1057      return new Schema(schemaEntry);
1058    }
1059  }
1060
1061
1062
1063  /**
1064   * Reads schema information from one or more files containing the schema
1065   * represented in LDIF form, with the definitions represented in the form
1066   * described in section 4.1 of RFC 4512.  Each file should contain a single
1067   * entry.  Any unparsable schema elements will be silently ignored.
1068   *
1069   * @param  schemaFiles  The paths to the LDIF files containing the schema
1070   *                      information to be read.  At least one file must be
1071   *                      specified.  If multiple files are specified, then they
1072   *                      will be processed in the order in which they have been
1073   *                      listed.
1074   *
1075   * @return  The schema read from the specified schema files, or {@code null}
1076   *          if none of the files contains any LDIF data to be read.
1077   *
1078   * @throws  IOException  If a problem occurs while attempting to read from
1079   *                       any of the specified files.
1080   *
1081   * @throws  LDIFException  If a problem occurs while attempting to parse the
1082   *                         contents of any of the schema files.
1083   */
1084  public static Schema getSchema(final String... schemaFiles)
1085         throws IOException, LDIFException
1086  {
1087    Validator.ensureNotNull(schemaFiles);
1088    Validator.ensureFalse(schemaFiles.length == 0);
1089
1090    final ArrayList<File> files = new ArrayList<>(schemaFiles.length);
1091    for (final String s : schemaFiles)
1092    {
1093      files.add(new File(s));
1094    }
1095
1096    return getSchema(files);
1097  }
1098
1099
1100
1101  /**
1102   * Reads schema information from one or more files containing the schema
1103   * represented in LDIF form, with the definitions represented in the form
1104   * described in section 4.1 of RFC 4512.  Each file should contain a single
1105   * entry.  Any unparsable schema elements will be silently ignored.
1106   *
1107   * @param  schemaFiles  The paths to the LDIF files containing the schema
1108   *                      information to be read.  At least one file must be
1109   *                      specified.  If multiple files are specified, then they
1110   *                      will be processed in the order in which they have been
1111   *                      listed.
1112   *
1113   * @return  The schema read from the specified schema files, or {@code null}
1114   *          if none of the files contains any LDIF data to be read.
1115   *
1116   * @throws  IOException  If a problem occurs while attempting to read from
1117   *                       any of the specified files.
1118   *
1119   * @throws  LDIFException  If a problem occurs while attempting to parse the
1120   *                         contents of any of the schema files.
1121   */
1122  public static Schema getSchema(final File... schemaFiles)
1123         throws IOException, LDIFException
1124  {
1125    Validator.ensureNotNull(schemaFiles);
1126    Validator.ensureFalse(schemaFiles.length == 0);
1127
1128    return getSchema(Arrays.asList(schemaFiles));
1129  }
1130
1131
1132
1133  /**
1134   * Reads schema information from one or more files containing the schema
1135   * represented in LDIF form, with the definitions represented in the form
1136   * described in section 4.1 of RFC 4512.  Each file should contain a single
1137   * entry.  Any unparsable schema elements will be silently ignored.
1138   *
1139   * @param  schemaFiles  The paths to the LDIF files containing the schema
1140   *                      information to be read.  At least one file must be
1141   *                      specified.  If multiple files are specified, then they
1142   *                      will be processed in the order in which they have been
1143   *                      listed.
1144   *
1145   * @return  The schema read from the specified schema files, or {@code null}
1146   *          if none of the files contains any LDIF data to be read.
1147   *
1148   * @throws  IOException  If a problem occurs while attempting to read from
1149   *                       any of the specified files.
1150   *
1151   * @throws  LDIFException  If a problem occurs while attempting to parse the
1152   *                         contents of any of the schema files.
1153   */
1154  public static Schema getSchema(final List<File> schemaFiles)
1155         throws IOException, LDIFException
1156  {
1157    return getSchema(schemaFiles, false);
1158  }
1159
1160
1161
1162  /**
1163   * Reads schema information from one or more files containing the schema
1164   * represented in LDIF form, with the definitions represented in the form
1165   * described in section 4.1 of RFC 4512.  Each file should contain a single
1166   * entry.
1167   *
1168   * @param  schemaFiles               The paths to the LDIF files containing
1169   *                                   the schema information to be read.  At
1170   *                                   least one file must be specified.  If
1171   *                                   multiple files are specified, then they
1172   *                                   will be processed in the order in which
1173   *                                   they have been listed.
1174   * @param  throwOnUnparsableElement  Indicates whether to throw an exception
1175   *                                   if the schema entry that is retrieved has
1176   *                                   one or more unparsable schema elements.
1177   *
1178   * @return  The schema read from the specified schema files, or {@code null}
1179   *          if none of the files contains any LDIF data to be read.
1180   *
1181   * @throws  IOException  If a problem occurs while attempting to read from
1182   *                       any of the specified files.
1183   *
1184   * @throws  LDIFException  If a problem occurs while attempting to parse the
1185   *                         contents of any of the schema files.  If
1186   *                         {@code throwOnUnparsableElement} is {@code true},
1187   *                         then this may also be thrown if any of the schema
1188   *                         files contains any unparsable schema elements.
1189   */
1190  public static Schema getSchema(final List<File> schemaFiles,
1191                                 final boolean throwOnUnparsableElement)
1192         throws IOException, LDIFException
1193  {
1194    Validator.ensureNotNull(schemaFiles);
1195    Validator.ensureFalse(schemaFiles.isEmpty());
1196
1197    Entry schemaEntry = null;
1198    for (final File f : schemaFiles)
1199    {
1200      final LDIFReader ldifReader = new LDIFReader(f);
1201
1202      try
1203      {
1204        final Entry e = ldifReader.readEntry();
1205        if (e == null)
1206        {
1207          continue;
1208        }
1209
1210        e.addAttribute("objectClass", "top", "ldapSubentry", "subschema");
1211
1212        if (schemaEntry == null)
1213        {
1214          schemaEntry = e;
1215        }
1216        else
1217        {
1218          for (final Attribute a : e.getAttributes())
1219          {
1220            schemaEntry.addAttribute(a);
1221          }
1222        }
1223      }
1224      finally
1225      {
1226        ldifReader.close();
1227      }
1228    }
1229
1230    if (schemaEntry == null)
1231    {
1232      return null;
1233    }
1234
1235    if (throwOnUnparsableElement)
1236    {
1237      try
1238      {
1239        return parseSchemaEntry(schemaEntry);
1240      }
1241      catch (final LDAPException e)
1242      {
1243        Debug.debugException(e);
1244        throw new LDIFException(e.getMessage(), 0, false, e);
1245      }
1246    }
1247    else
1248    {
1249      return new Schema(schemaEntry);
1250    }
1251  }
1252
1253
1254
1255  /**
1256   * Reads schema information from the provided input stream.  The information
1257   * should be in LDIF form, with the definitions represented in the form
1258   * described in section 4.1 of RFC 4512.  Only a single entry will be read
1259   * from the input stream, and it will be closed at the end of this method.
1260   *
1261   * @param  inputStream  The input stream from which the schema entry will be
1262   *                      read.  It must not be {@code null}, and it will be
1263   *                      closed when this method returns.
1264   *
1265   * @return  The schema read from the provided input stream, or {@code null} if
1266   *          the end of the input stream is reached without reading any data.
1267   *
1268   * @throws  IOException  If a problem is encountered while attempting to read
1269   *                       from the provided input stream.
1270   *
1271   * @throws  LDIFException  If a problem occurs while attempting to parse the
1272   *                         data read as LDIF.
1273   */
1274  public static Schema getSchema(final InputStream inputStream)
1275         throws IOException, LDIFException
1276  {
1277    Validator.ensureNotNull(inputStream);
1278
1279    final LDIFReader ldifReader = new LDIFReader(inputStream);
1280
1281    try
1282    {
1283      final Entry e = ldifReader.readEntry();
1284      if (e == null)
1285      {
1286        return null;
1287      }
1288      else
1289      {
1290        return new Schema(e);
1291      }
1292    }
1293    finally
1294    {
1295      ldifReader.close();
1296    }
1297  }
1298
1299
1300
1301  /**
1302   * Retrieves a schema object that contains definitions for a number of
1303   * standard attribute types and object classes from LDAP-related RFCs and
1304   * Internet Drafts.
1305   *
1306   * @return  A schema object that contains definitions for a number of standard
1307   *          attribute types and object classes from LDAP-related RFCs and
1308   *          Internet Drafts.
1309   *
1310   * @throws  LDAPException  If a problem occurs while attempting to obtain or
1311   *                         parse the default standard schema definitions.
1312   */
1313  public static Schema getDefaultStandardSchema()
1314         throws LDAPException
1315  {
1316    final Schema s = DEFAULT_STANDARD_SCHEMA.get();
1317    if (s != null)
1318    {
1319      return s;
1320    }
1321
1322    synchronized (DEFAULT_STANDARD_SCHEMA)
1323    {
1324      try
1325      {
1326        final ClassLoader classLoader = Schema.class.getClassLoader();
1327        final InputStream inputStream =
1328             classLoader.getResourceAsStream(DEFAULT_SCHEMA_RESOURCE_PATH);
1329        final LDIFReader ldifReader = new LDIFReader(inputStream);
1330        final Entry schemaEntry = ldifReader.readEntry();
1331        ldifReader.close();
1332
1333        final Schema schema = new Schema(schemaEntry);
1334        DEFAULT_STANDARD_SCHEMA.set(schema);
1335        return schema;
1336      }
1337      catch (final Exception e)
1338      {
1339        Debug.debugException(e);
1340        throw new LDAPException(ResultCode.LOCAL_ERROR,
1341             ERR_SCHEMA_CANNOT_LOAD_DEFAULT_DEFINITIONS.get(
1342                  StaticUtils.getExceptionMessage(e)),
1343             e);
1344      }
1345    }
1346  }
1347
1348
1349
1350  /**
1351   * Retrieves a schema containing all of the elements of each of the provided
1352   * schemas.
1353   *
1354   * @param  schemas  The schemas to be merged.  It must not be {@code null} or
1355   *                  empty.
1356   *
1357   * @return  A merged representation of the provided schemas.
1358   */
1359  public static Schema mergeSchemas(final Schema... schemas)
1360  {
1361    if ((schemas == null) || (schemas.length == 0))
1362    {
1363      return null;
1364    }
1365    else if (schemas.length == 1)
1366    {
1367      return schemas[0];
1368    }
1369
1370    final LinkedHashMap<String,String> asMap =
1371         new LinkedHashMap<>(StaticUtils.computeMapCapacity(100));
1372    final LinkedHashMap<String,String> atMap =
1373         new LinkedHashMap<>(StaticUtils.computeMapCapacity(100));
1374    final LinkedHashMap<String,String> dcrMap =
1375         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
1376    final LinkedHashMap<Integer,String> dsrMap =
1377         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
1378    final LinkedHashMap<String,String> mrMap =
1379         new LinkedHashMap<>(StaticUtils.computeMapCapacity(100));
1380    final LinkedHashMap<String,String> mruMap =
1381         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
1382    final LinkedHashMap<String,String> nfMap =
1383         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
1384    final LinkedHashMap<String,String> ocMap =
1385         new LinkedHashMap<>(StaticUtils.computeMapCapacity(100));
1386
1387    for (final Schema s : schemas)
1388    {
1389      for (final AttributeSyntaxDefinition as : s.asSet)
1390      {
1391        asMap.put(StaticUtils.toLowerCase(as.getOID()), as.toString());
1392      }
1393
1394      for (final AttributeTypeDefinition at : s.atSet)
1395      {
1396        atMap.put(StaticUtils.toLowerCase(at.getOID()), at.toString());
1397      }
1398
1399      for (final DITContentRuleDefinition dcr : s.dcrSet)
1400      {
1401        dcrMap.put(StaticUtils.toLowerCase(dcr.getOID()), dcr.toString());
1402      }
1403
1404      for (final DITStructureRuleDefinition dsr : s.dsrSet)
1405      {
1406        dsrMap.put(dsr.getRuleID(), dsr.toString());
1407      }
1408
1409      for (final MatchingRuleDefinition mr : s.mrSet)
1410      {
1411        mrMap.put(StaticUtils.toLowerCase(mr.getOID()), mr.toString());
1412      }
1413
1414      for (final MatchingRuleUseDefinition mru : s.mruSet)
1415      {
1416        mruMap.put(StaticUtils.toLowerCase(mru.getOID()), mru.toString());
1417      }
1418
1419      for (final NameFormDefinition nf : s.nfSet)
1420      {
1421        nfMap.put(StaticUtils.toLowerCase(nf.getOID()), nf.toString());
1422      }
1423
1424      for (final ObjectClassDefinition oc : s.ocSet)
1425      {
1426        ocMap.put(StaticUtils.toLowerCase(oc.getOID()), oc.toString());
1427      }
1428    }
1429
1430    final Entry e = new Entry(schemas[0].getSchemaEntry().getDN());
1431
1432    final Attribute ocAttr =
1433         schemas[0].getSchemaEntry().getObjectClassAttribute();
1434    if (ocAttr == null)
1435    {
1436      e.addAttribute("objectClass", "top", "ldapSubEntry", "subschema");
1437    }
1438    else
1439    {
1440      e.addAttribute(ocAttr);
1441    }
1442
1443    if (! asMap.isEmpty())
1444    {
1445      final String[] values = new String[asMap.size()];
1446      e.addAttribute(ATTR_ATTRIBUTE_SYNTAX, asMap.values().toArray(values));
1447    }
1448
1449    if (! mrMap.isEmpty())
1450    {
1451      final String[] values = new String[mrMap.size()];
1452      e.addAttribute(ATTR_MATCHING_RULE, mrMap.values().toArray(values));
1453    }
1454
1455    if (! atMap.isEmpty())
1456    {
1457      final String[] values = new String[atMap.size()];
1458      e.addAttribute(ATTR_ATTRIBUTE_TYPE, atMap.values().toArray(values));
1459    }
1460
1461    if (! ocMap.isEmpty())
1462    {
1463      final String[] values = new String[ocMap.size()];
1464      e.addAttribute(ATTR_OBJECT_CLASS, ocMap.values().toArray(values));
1465    }
1466
1467    if (! dcrMap.isEmpty())
1468    {
1469      final String[] values = new String[dcrMap.size()];
1470      e.addAttribute(ATTR_DIT_CONTENT_RULE, dcrMap.values().toArray(values));
1471    }
1472
1473    if (! dsrMap.isEmpty())
1474    {
1475      final String[] values = new String[dsrMap.size()];
1476      e.addAttribute(ATTR_DIT_STRUCTURE_RULE, dsrMap.values().toArray(values));
1477    }
1478
1479    if (! nfMap.isEmpty())
1480    {
1481      final String[] values = new String[nfMap.size()];
1482      e.addAttribute(ATTR_NAME_FORM, nfMap.values().toArray(values));
1483    }
1484
1485    if (! mruMap.isEmpty())
1486    {
1487      final String[] values = new String[mruMap.size()];
1488      e.addAttribute(ATTR_MATCHING_RULE_USE, mruMap.values().toArray(values));
1489    }
1490
1491    return new Schema(e);
1492  }
1493
1494
1495
1496  /**
1497   * Retrieves the entry used to create this schema object.
1498   *
1499   * @return  The entry used to create this schema object.
1500   */
1501  public ReadOnlyEntry getSchemaEntry()
1502  {
1503    return schemaEntry;
1504  }
1505
1506
1507
1508  /**
1509   * Retrieves the value of the subschemaSubentry attribute from the specified
1510   * entry using the provided connection.
1511   *
1512   * @param  connection  The connection to use in order to perform the search.
1513   *                     It must not be {@code null}.
1514   * @param  entryDN     The DN of the entry from which to retrieve the
1515   *                     subschemaSubentry attribute.  It may be {@code null} or
1516   *                     an empty string in order to retrieve the value from the
1517   *                     server's root DSE.
1518   *
1519   * @return  The value of the subschemaSubentry attribute from the specified
1520   *          entry, or {@code null} if it is not available for some reason
1521   *          (e.g., the client does not have permission to read the target
1522   *          entry or the subschemaSubentry attribute).
1523   *
1524   * @throws  LDAPException  If a problem occurs while attempting to retrieve
1525   *                         the specified entry.
1526   */
1527  public static String getSubschemaSubentryDN(final LDAPConnection connection,
1528                                              final String entryDN)
1529         throws LDAPException
1530  {
1531    Validator.ensureNotNull(connection);
1532
1533    final Entry e;
1534    if (entryDN == null)
1535    {
1536      e = connection.getEntry("", SUBSCHEMA_SUBENTRY_REQUEST_ATTRS);
1537    }
1538    else
1539    {
1540      e = connection.getEntry(entryDN, SUBSCHEMA_SUBENTRY_REQUEST_ATTRS);
1541    }
1542
1543    if (e == null)
1544    {
1545      return null;
1546    }
1547
1548    return e.getAttributeValue(ATTR_SUBSCHEMA_SUBENTRY);
1549  }
1550
1551
1552
1553  /**
1554   * Retrieves the set of attribute syntax definitions contained in the server
1555   * schema.
1556   *
1557   * @return  The set of attribute syntax definitions contained in the server
1558   *          schema.
1559   */
1560  public Set<AttributeSyntaxDefinition> getAttributeSyntaxes()
1561  {
1562    return asSet;
1563  }
1564
1565
1566
1567  /**
1568   * Retrieves the attribute syntax with the specified OID from the server
1569   * schema.
1570   *
1571   * @param  oid  The OID of the attribute syntax to retrieve.  It must not be
1572   *              {@code null}.  It may optionally include a minimum upper bound
1573   *              (as may appear when the syntax OID is included in an attribute
1574   *              type definition), but if it does then that portion will be
1575   *              ignored when retrieving the attribute syntax.
1576   *
1577   * @return  The requested attribute syntax, or {@code null} if there is no
1578   *          such syntax defined in the server schema.
1579   */
1580  public AttributeSyntaxDefinition getAttributeSyntax(final String oid)
1581  {
1582    Validator.ensureNotNull(oid);
1583
1584    final String lowerOID = StaticUtils.toLowerCase(oid);
1585    final int    curlyPos = lowerOID.indexOf('{');
1586
1587    if (curlyPos > 0)
1588    {
1589      return asMap.get(lowerOID.substring(0, curlyPos));
1590    }
1591    else
1592    {
1593      return asMap.get(lowerOID);
1594    }
1595  }
1596
1597
1598
1599  /**
1600   * Retrieves the set of attribute type definitions contained in the server
1601   * schema.
1602   *
1603   * @return  The set of attribute type definitions contained in the server
1604   *          schema.
1605   */
1606  public Set<AttributeTypeDefinition> getAttributeTypes()
1607  {
1608    return atSet;
1609  }
1610
1611
1612
1613  /**
1614   * Retrieves the set of operational attribute type definitions (i.e., those
1615   * definitions with a usage of directoryOperation, distributedOperation, or
1616   * dSAOperation) contained in the  server  schema.
1617   *
1618   * @return  The set of operational attribute type definitions contained in the
1619   *          server schema.
1620   */
1621  public Set<AttributeTypeDefinition> getOperationalAttributeTypes()
1622  {
1623    return operationalATSet;
1624  }
1625
1626
1627
1628  /**
1629   * Retrieves the set of user attribute type definitions (i.e., those
1630   * definitions with a usage of userApplications) contained in the  server
1631   * schema.
1632   *
1633   * @return  The set of user attribute type definitions contained in the server
1634   *          schema.
1635   */
1636  public Set<AttributeTypeDefinition> getUserAttributeTypes()
1637  {
1638    return userATSet;
1639  }
1640
1641
1642
1643  /**
1644   * Retrieves the attribute type with the specified name or OID from the server
1645   * schema.
1646   *
1647   * @param  name  The name or OID of the attribute type to retrieve.  It must
1648   *               not be {@code null}.
1649   *
1650   * @return  The requested attribute type, or {@code null} if there is no
1651   *          such attribute type defined in the server schema.
1652   */
1653  public AttributeTypeDefinition getAttributeType(final String name)
1654  {
1655    Validator.ensureNotNull(name);
1656
1657    return atMap.get(StaticUtils.toLowerCase(name));
1658  }
1659
1660
1661
1662  /**
1663   * Retrieves a list of all subordinate attribute type definitions for the
1664   * provided attribute type definition.
1665   *
1666   * @param  d  The attribute type definition for which to retrieve all
1667   *            subordinate attribute types.  It must not be {@code null}.
1668   *
1669   * @return  A list of all subordinate attribute type definitions for the
1670   *          provided attribute type definition, or an empty list if it does
1671   *          not have any subordinate types or the provided attribute type is
1672   *          not defined in the schema.
1673   */
1674  public List<AttributeTypeDefinition> getSubordinateAttributeTypes(
1675                                            final AttributeTypeDefinition d)
1676  {
1677    Validator.ensureNotNull(d);
1678
1679    final List<AttributeTypeDefinition> l = subordinateAttributeTypes.get(d);
1680    if (l == null)
1681    {
1682      return Collections.emptyList();
1683    }
1684    else
1685    {
1686      return Collections.unmodifiableList(l);
1687    }
1688  }
1689
1690
1691
1692  /**
1693   * Retrieves the set of DIT content rule definitions contained in the server
1694   * schema.
1695   *
1696   * @return  The set of DIT content rule definitions contained in the server
1697   *          schema.
1698   */
1699  public Set<DITContentRuleDefinition> getDITContentRules()
1700  {
1701    return dcrSet;
1702  }
1703
1704
1705
1706  /**
1707   * Retrieves the DIT content rule with the specified name or OID from the
1708   * server schema.
1709   *
1710   * @param  name  The name or OID of the DIT content rule to retrieve.  It must
1711   *               not be {@code null}.
1712   *
1713   * @return  The requested DIT content rule, or {@code null} if there is no
1714   *          such rule defined in the server schema.
1715   */
1716  public DITContentRuleDefinition getDITContentRule(final String name)
1717  {
1718    Validator.ensureNotNull(name);
1719
1720    return dcrMap.get(StaticUtils.toLowerCase(name));
1721  }
1722
1723
1724
1725  /**
1726   * Retrieves the set of DIT structure rule definitions contained in the server
1727   * schema.
1728   *
1729   * @return  The set of DIT structure rule definitions contained in the server
1730   *          schema.
1731   */
1732  public Set<DITStructureRuleDefinition> getDITStructureRules()
1733  {
1734    return dsrSet;
1735  }
1736
1737
1738
1739  /**
1740   * Retrieves the DIT content rule with the specified rule ID from the server
1741   * schema.
1742   *
1743   * @param  ruleID  The rule ID for the DIT structure rule to retrieve.
1744   *
1745   * @return  The requested DIT structure rule, or {@code null} if there is no
1746   *          such rule defined in the server schema.
1747   */
1748  public DITStructureRuleDefinition getDITStructureRuleByID(final int ruleID)
1749  {
1750    return dsrMapByID.get(ruleID);
1751  }
1752
1753
1754
1755  /**
1756   * Retrieves the DIT content rule with the specified name from the server
1757   * schema.
1758   *
1759   * @param  ruleName  The name of the DIT structure rule to retrieve.  It must
1760   *                   not be {@code null}.
1761   *
1762   * @return  The requested DIT structure rule, or {@code null} if there is no
1763   *          such rule defined in the server schema.
1764   */
1765  public DITStructureRuleDefinition getDITStructureRuleByName(
1766                                         final String ruleName)
1767  {
1768    Validator.ensureNotNull(ruleName);
1769
1770    return dsrMapByName.get(StaticUtils.toLowerCase(ruleName));
1771  }
1772
1773
1774
1775  /**
1776   * Retrieves the DIT content rule associated with the specified name form from
1777   * the server schema.
1778   *
1779   * @param  nameForm  The name or OID of the name form for which to retrieve
1780   *                   the associated DIT structure rule.
1781   *
1782   * @return  The requested DIT structure rule, or {@code null} if there is no
1783   *          such rule defined in the server schema.
1784   */
1785  public DITStructureRuleDefinition getDITStructureRuleByNameForm(
1786                                         final String nameForm)
1787  {
1788    Validator.ensureNotNull(nameForm);
1789
1790    return dsrMapByNameForm.get(StaticUtils.toLowerCase(nameForm));
1791  }
1792
1793
1794
1795  /**
1796   * Retrieves the set of matching rule definitions contained in the server
1797   * schema.
1798   *
1799   * @return  The set of matching rule definitions contained in the server
1800   *          schema.
1801   */
1802  public Set<MatchingRuleDefinition> getMatchingRules()
1803  {
1804    return mrSet;
1805  }
1806
1807
1808
1809  /**
1810   * Retrieves the matching rule with the specified name or OID from the server
1811   * schema.
1812   *
1813   * @param  name  The name or OID of the matching rule to retrieve.  It must
1814   *               not be {@code null}.
1815   *
1816   * @return  The requested matching rule, or {@code null} if there is no
1817   *          such rule defined in the server schema.
1818   */
1819  public MatchingRuleDefinition getMatchingRule(final String name)
1820  {
1821    Validator.ensureNotNull(name);
1822
1823    return mrMap.get(StaticUtils.toLowerCase(name));
1824  }
1825
1826
1827
1828  /**
1829   * Retrieves the set of matching rule use definitions contained in the server
1830   * schema.
1831   *
1832   * @return  The set of matching rule use definitions contained in the server
1833   *          schema.
1834   */
1835  public Set<MatchingRuleUseDefinition> getMatchingRuleUses()
1836  {
1837    return mruSet;
1838  }
1839
1840
1841
1842  /**
1843   * Retrieves the matching rule use with the specified name or OID from the
1844   * server schema.
1845   *
1846   * @param  name  The name or OID of the matching rule use to retrieve.  It
1847   *               must not be {@code null}.
1848   *
1849   * @return  The requested matching rule, or {@code null} if there is no
1850   *          such matching rule use defined in the server schema.
1851   */
1852  public MatchingRuleUseDefinition getMatchingRuleUse(final String name)
1853  {
1854    Validator.ensureNotNull(name);
1855
1856    return mruMap.get(StaticUtils.toLowerCase(name));
1857  }
1858
1859
1860
1861  /**
1862   * Retrieves the set of name form definitions contained in the server schema.
1863   *
1864   * @return  The set of name form definitions contained in the server schema.
1865   */
1866  public Set<NameFormDefinition> getNameForms()
1867  {
1868    return nfSet;
1869  }
1870
1871
1872
1873  /**
1874   * Retrieves the name form with the specified name or OID from the server
1875   * schema.
1876   *
1877   * @param  name  The name or OID of the name form to retrieve.  It must not be
1878   *               {@code null}.
1879   *
1880   * @return  The requested name form, or {@code null} if there is no
1881   *          such rule defined in the server schema.
1882   */
1883  public NameFormDefinition getNameFormByName(final String name)
1884  {
1885    Validator.ensureNotNull(name);
1886
1887    return nfMapByName.get(StaticUtils.toLowerCase(name));
1888  }
1889
1890
1891
1892  /**
1893   * Retrieves the name form associated with the specified structural object
1894   * class from the server schema.
1895   *
1896   * @param  objectClass  The name or OID of the structural object class for
1897   *                      which to retrieve the associated name form.  It must
1898   *                      not be {@code null}.
1899   *
1900   * @return  The requested name form, or {@code null} if there is no
1901   *          such rule defined in the server schema.
1902   */
1903  public NameFormDefinition getNameFormByObjectClass(final String objectClass)
1904  {
1905    Validator.ensureNotNull(objectClass);
1906
1907    return nfMapByOC.get(StaticUtils.toLowerCase(objectClass));
1908  }
1909
1910
1911
1912  /**
1913   * Retrieves the set of object class definitions contained in the server
1914   * schema.
1915   *
1916   * @return  The set of object class definitions contained in the server
1917   *          schema.
1918   */
1919  public Set<ObjectClassDefinition> getObjectClasses()
1920  {
1921    return ocSet;
1922  }
1923
1924
1925
1926  /**
1927   * Retrieves the set of abstract object class definitions contained in the
1928   * server schema.
1929   *
1930   * @return  The set of abstract object class definitions contained in the
1931   *          server schema.
1932   */
1933  public Set<ObjectClassDefinition> getAbstractObjectClasses()
1934  {
1935    return abstractOCSet;
1936  }
1937
1938
1939
1940  /**
1941   * Retrieves the set of auxiliary object class definitions contained in the
1942   * server schema.
1943   *
1944   * @return  The set of auxiliary object class definitions contained in the
1945   *          server schema.
1946   */
1947  public Set<ObjectClassDefinition> getAuxiliaryObjectClasses()
1948  {
1949    return auxiliaryOCSet;
1950  }
1951
1952
1953
1954  /**
1955   * Retrieves the set of structural object class definitions contained in the
1956   * server schema.
1957   *
1958   * @return  The set of structural object class definitions contained in the
1959   *          server schema.
1960   */
1961  public Set<ObjectClassDefinition> getStructuralObjectClasses()
1962  {
1963    return structuralOCSet;
1964  }
1965
1966
1967
1968  /**
1969   * Retrieves the object class with the specified name or OID from the server
1970   * schema.
1971   *
1972   * @param  name  The name or OID of the object class to retrieve.  It must
1973   *               not be {@code null}.
1974   *
1975   * @return  The requested object class, or {@code null} if there is no such
1976   *          class defined in the server schema.
1977   */
1978  public ObjectClassDefinition getObjectClass(final String name)
1979  {
1980    Validator.ensureNotNull(name);
1981
1982    return ocMap.get(StaticUtils.toLowerCase(name));
1983  }
1984
1985
1986
1987  /**
1988   * Retrieves a hash code for this schema object.
1989   *
1990   * @return  A hash code for this schema object.
1991   */
1992  @Override()
1993  public int hashCode()
1994  {
1995    int hc;
1996    try
1997    {
1998      hc = schemaEntry.getParsedDN().hashCode();
1999    }
2000    catch (final Exception e)
2001    {
2002      Debug.debugException(e);
2003      hc = StaticUtils.toLowerCase(schemaEntry.getDN()).hashCode();
2004    }
2005
2006    Attribute a = schemaEntry.getAttribute(ATTR_ATTRIBUTE_SYNTAX);
2007    if (a != null)
2008    {
2009      hc += a.hashCode();
2010    }
2011
2012    a = schemaEntry.getAttribute(ATTR_MATCHING_RULE);
2013    if (a != null)
2014    {
2015      hc += a.hashCode();
2016    }
2017
2018    a = schemaEntry.getAttribute(ATTR_ATTRIBUTE_TYPE);
2019    if (a != null)
2020    {
2021      hc += a.hashCode();
2022    }
2023
2024    a = schemaEntry.getAttribute(ATTR_OBJECT_CLASS);
2025    if (a != null)
2026    {
2027      hc += a.hashCode();
2028    }
2029
2030    a = schemaEntry.getAttribute(ATTR_NAME_FORM);
2031    if (a != null)
2032    {
2033      hc += a.hashCode();
2034    }
2035
2036    a = schemaEntry.getAttribute(ATTR_DIT_CONTENT_RULE);
2037    if (a != null)
2038    {
2039      hc += a.hashCode();
2040    }
2041
2042    a = schemaEntry.getAttribute(ATTR_DIT_STRUCTURE_RULE);
2043    if (a != null)
2044    {
2045      hc += a.hashCode();
2046    }
2047
2048    a = schemaEntry.getAttribute(ATTR_MATCHING_RULE_USE);
2049    if (a != null)
2050    {
2051      hc += a.hashCode();
2052    }
2053
2054    return hc;
2055  }
2056
2057
2058
2059  /**
2060   * Indicates whether the provided object is equal to this schema object.
2061   *
2062   * @param  o  The object for which to make the determination.
2063   *
2064   * @return  {@code true} if the provided object is equal to this schema
2065   *          object, or {@code false} if not.
2066   */
2067  @Override()
2068  public boolean equals(final Object o)
2069  {
2070    if (o == null)
2071    {
2072      return false;
2073    }
2074
2075    if (o == this)
2076    {
2077      return true;
2078    }
2079
2080    if (! (o instanceof Schema))
2081    {
2082      return false;
2083    }
2084
2085    final Schema s = (Schema) o;
2086
2087    try
2088    {
2089      if (! schemaEntry.getParsedDN().equals(s.schemaEntry.getParsedDN()))
2090      {
2091        return false;
2092      }
2093    }
2094    catch (final Exception e)
2095    {
2096      Debug.debugException(e);
2097      if (! schemaEntry.getDN().equalsIgnoreCase(s.schemaEntry.getDN()))
2098      {
2099        return false;
2100      }
2101    }
2102
2103    return (asSet.equals(s.asSet) &&
2104         mrSet.equals(s.mrSet) &&
2105         atSet.equals(s.atSet) &&
2106         ocSet.equals(s.ocSet) &&
2107         nfSet.equals(s.nfSet) &&
2108         dcrSet.equals(s.dcrSet) &&
2109         dsrSet.equals(s.dsrSet) &&
2110         mruSet.equals(s.mruSet));
2111  }
2112
2113
2114
2115  /**
2116   * Retrieves a string representation of the associated schema entry.
2117   *
2118   * @return  A string representation of the associated schema entry.
2119   */
2120  @Override()
2121  public String toString()
2122  {
2123    return schemaEntry.toString();
2124  }
2125}