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