001/*
002 * Copyright 2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 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) 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.unboundidds.tools;
037
038
039
040import java.io.OutputStream;
041import java.util.ArrayList;
042import java.util.Arrays;
043import java.util.HashMap;
044import java.util.HashSet;
045import java.util.Iterator;
046import java.util.LinkedHashMap;
047import java.util.List;
048import java.util.Map;
049import java.util.Set;
050import java.util.TreeMap;
051import java.util.TreeSet;
052import java.util.concurrent.atomic.AtomicInteger;
053import java.util.concurrent.atomic.AtomicReference;
054
055import com.unboundid.ldap.sdk.Entry;
056import com.unboundid.ldap.sdk.LDAPConnection;
057import com.unboundid.ldap.sdk.LDAPException;
058import com.unboundid.ldap.sdk.ResultCode;
059import com.unboundid.ldap.sdk.RootDSE;
060import com.unboundid.ldap.sdk.SearchRequest;
061import com.unboundid.ldap.sdk.SearchScope;
062import com.unboundid.ldap.sdk.Version;
063import com.unboundid.ldap.sdk.schema.AttributeSyntaxDefinition;
064import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
065import com.unboundid.ldap.sdk.schema.DITContentRuleDefinition;
066import com.unboundid.ldap.sdk.schema.DITStructureRuleDefinition;
067import com.unboundid.ldap.sdk.schema.MatchingRuleDefinition;
068import com.unboundid.ldap.sdk.schema.MatchingRuleUseDefinition;
069import com.unboundid.ldap.sdk.schema.NameFormDefinition;
070import com.unboundid.ldap.sdk.schema.ObjectClassDefinition;
071import com.unboundid.ldap.sdk.schema.Schema;
072import com.unboundid.ldap.sdk.unboundidds.controls.
073            ExtendedSchemaInfoRequestControl;
074import com.unboundid.util.Debug;
075import com.unboundid.util.MultiServerLDAPCommandLineTool;
076import com.unboundid.util.NotNull;
077import com.unboundid.util.Nullable;
078import com.unboundid.util.OID;
079import com.unboundid.util.StaticUtils;
080import com.unboundid.util.ThreadSafety;
081import com.unboundid.util.ThreadSafetyLevel;
082import com.unboundid.util.args.ArgumentException;
083import com.unboundid.util.args.ArgumentParser;
084import com.unboundid.util.args.BooleanArgument;
085import com.unboundid.util.args.DNArgument;
086import com.unboundid.util.args.StringArgument;
087
088import static com.unboundid.ldap.sdk.unboundidds.tools.ToolMessages.*;
089
090
091
092/**
093 * This class implements a command-line tool that can be used to retrieve the
094 * schemas from two LDAP servers and identify any differences between them.
095 * <BR>
096 * <BLOCKQUOTE>
097 *   <B>NOTE:</B>  This class, and other classes within the
098 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
099 *   supported for use against Ping Identity, UnboundID, and
100 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
101 *   for proprietary functionality or for external specifications that are not
102 *   considered stable or mature enough to be guaranteed to work in an
103 *   interoperable way with other types of LDAP servers.
104 * </BLOCKQUOTE>
105 * <BR>
106 * Comparisons that this tool may perform include:
107 * <UL>
108 *   <LI>Definitions that are present in one server but not another.</LI>
109 *   <LI>Corresponding definitions with the same OID but different names or sets
110 *       of names.</LI>
111 *   <LI>Corresponding definitions with different descriptions, obsolete state,
112 *       or sets of extensions.</LI>
113 *   <LI>Corresponding attribute types with differences in syntaxes, matching
114 *       rules, superior type, single-valued/multivalued behavior, usage,
115 *       collective state, or NO-USER-MODIFICATION state.</LI>
116 *   <LI>Corresponding object classes with differences in required or optional
117 *       attributes, superior class, or object class type.</LI>
118 *   <LI>Corresponding DIT content rules with differences in required, optional,
119 *       or prohibited attributes, or allowed auxiliary classes.</LI>
120 *   <LI>Corresponding name forms with differences in structural class,
121 *       required attributes, or optional attributes.</LI>
122 *   <LI>Corresponding DIT structure rules with different name form IDs or
123 *       superior rule IDs.</LI>
124 *   <LI>Corresponding matching rule uses with different sets of applicable
125 *       attribute types.</LI>
126 * </UL>
127 */
128@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
129public final class CompareLDAPSchemas
130       extends MultiServerLDAPCommandLineTool
131{
132  /**
133   * The column at which long lines should be wrapped.
134   */
135  private static final int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1;
136
137
138
139  /**
140   * The index number used to reference the first server.
141   */
142  private static final int FIRST_SERVER_INDEX = 0;
143
144
145
146  /**
147   * The index number used to reference the second server.
148   */
149  private static final int SECOND_SERVER_INDEX = 1;
150
151
152
153  /**
154   * The name of the command-line argument used to indicate that the tool should
155   * not examine schema elements that have an extension with a given name and
156   * value.
157   */
158  @NotNull private static final String
159       ARG_NAME_EXCLUDE_ELEMENTS_WITH_EXTENSION_VALUE =
160            "excludeElementsWithExtensionValue";
161
162
163
164  /**
165   * The name of the command-line argument used to indicate that the tool should
166   * not examine schema elements with names matching a specified prefix.
167   */
168  @NotNull private static final String
169       ARG_NAME_EXCLUDE_ELEMENTS_WITH_NAME_MATCHING_PREFIX =
170            "excludeElementsWithNameMatchingPrefix";
171
172
173
174  /**
175   * The name of the command-line argument used to specify the DN of the first
176   * server's subschema subentry.
177   */
178  @NotNull private static final String ARG_NAME_FIRST_SCHEMA_ENTRY_DN =
179       "firstSchemaEntryDN";
180
181
182
183  /**
184   * The name of the command-line argument used to indicate that the tool should
185   * use the get extended schema info request control if the server reports that
186   * it is supported.
187   */
188  @NotNull private static final String ARG_NAME_GET_EXTENDED_SCHEMA_INFO =
189       "getExtendedSchemaInfo";
190
191
192
193  /**
194   * The name of the command-line argument used to indicate that the tool should
195   * ignore differences in element descriptions.
196   */
197  @NotNull private static final String ARG_NAME_IGNORE_DESCRIPTIONS =
198       "ignoreDescriptions";
199
200
201
202  /**
203   * The name of the command-line argument used to indicate that the tool should
204   * ignore differences in element extensions.
205   */
206  @NotNull private static final String ARG_NAME_IGNORE_EXTENSIONS =
207       "ignoreExtensions";
208
209
210
211  /**
212   * The name of the command-line argument used to indicate that the tool should
213   * only examine schema elements that have an extension with a given name and
214   * value.
215   */
216  @NotNull private static final String
217       ARG_NAME_INCLUDE_ELEMENTS_WITH_EXTENSION_VALUE =
218            "includeElementsWithExtensionValue";
219
220
221
222  /**
223   * The name of the command-line argument used to indicate that the tool should
224   * only examine schema elements with names matching a specified prefix.
225   */
226  @NotNull private static final String
227       ARG_NAME_INCLUDE_ELEMENTS_WITH_NAME_MATCHING_PREFIX =
228            "includeElementsWithNameMatchingPrefix";
229
230
231
232  /**
233   * The name of the command-line argument used to specify the types of schema
234   * elements that the server should examine.
235   */
236  @NotNull private static final String ARG_NAME_SCHEMA_ELEMENT_TYPE =
237       "schemaElementType";
238
239
240
241  /**
242   * The name of the command-line argument used to specify the DN of the second
243   * server's subschema subentry.
244   */
245  @NotNull private static final String ARG_NAME_SECOND_SCHEMA_ENTRY_DN =
246       "secondSchemaEntryDN";
247
248
249
250  /**
251   * The name of the schema element type value that indicates that the tool
252   * should examine attribute syntaxes.
253   */
254  @NotNull private static final String SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAXES =
255       "attribute-syntaxes";
256
257
258
259  /**
260   * The name of the schema element type value that indicates that the tool
261   * should examine attribute types.
262   */
263  @NotNull private static final String SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPES =
264       "attribute-types";
265
266
267
268  /**
269   * The name of the schema element type value that indicates that the tool
270   * should examine DIT content rules.
271   */
272  @NotNull private static final String SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULES =
273       "dit-content-rules";
274
275
276
277  /**
278   * The name of the schema element type value that indicates that the tool
279   * should examine DIT structure rules.
280   */
281  @NotNull private static final String SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULES =
282       "dit-structure-rules";
283
284
285
286  /**
287   * The name of the schema element type value that indicates that the tool
288   * should examine matching rule uses.
289   */
290  @NotNull private static final String SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USES =
291       "matching-rule-uses";
292
293
294
295  /**
296   * The name of the schema element type value that indicates that the tool
297   * should examine matching rules.
298   */
299  @NotNull private static final String SCHEMA_ELEMENT_TYPE_MATCHING_RULES =
300       "matching-rules";
301
302
303
304  /**
305   * The name of the schema element type value that indicates that the tool
306   * should examine object classes.
307   */
308  @NotNull private static final String SCHEMA_ELEMENT_TYPE_OBJECT_CLASSES =
309       "object-classes";
310
311
312
313  /**
314   * The name of the schema element type value that indicates that the tool
315   * should examine name forms.
316   */
317  @NotNull private static final String SCHEMA_ELEMENT_TYPE_NAME_FORMS =
318       "name-forms";
319
320
321
322  // A reference to the argument parser for this tool.
323  @NotNull private final AtomicReference<ArgumentParser> parserRef;
324
325  // A reference to the completion message for this tool.
326  @NotNull private final AtomicReference<String> completionMessageRef;
327
328  // Indicates whether to ignore differences in schema element descriptions.
329  private boolean ignoreDescriptions;
330
331  // Indicates whether to ignore differences in schema element extensions.
332  private boolean ignoreExtensions;
333
334  // Indicates whether we may include or exclude schema elements based on their
335  // extensions.
336  private boolean includeOrExcludeBasedOnExtensions;
337
338  // Indicates whether we may include or exclude schema elements based on their
339  // name.
340  private boolean includeOrExcludeBasedOnName;
341
342  // A list of name prefixes for schema elements to exclude from the comparison.
343  @NotNull private final List<String> excludeNamePrefixes;
344
345  // A list of name prefixes for schema elements to include in the comparison.
346  @NotNull private final List<String> includeNamePrefixes;
347
348  // A map of schema extension values for schema elements to exclude from the
349  // comparison.
350  @NotNull private final Map<String,List<String>> excludeExtensionValues;
351
352  // A map of schema extension values for schema elements to include in the
353  // comparison.
354  @NotNull private final Map<String,List<String>> includeExtensionValues;
355
356  // The set of schema element types to examine.
357  @NotNull private final Set<String> schemaElementTypes;
358
359
360
361  /**
362   * Runs this tool with the provided set of arguments, using the default
363   * streams for standard output and standard error.
364   *
365   * @param  args  The command-line arguments to use to run this program.  It
366   *               must not be {@code null}, but may be empty.
367   */
368  public static void main(@NotNull final String... args)
369  {
370    final ResultCode resultCode = main(System.out, System.err, args);
371    if (resultCode != ResultCode.SUCCESS)
372    {
373      System.exit(resultCode.intValue());
374    }
375  }
376
377
378
379  /**
380   * Runs this tool with the provided set of arguments, using the provided
381   * streams for standard output and standard error.
382   *
383   * @param  out   The output stream to use for standard output.  It may be
384   *               {@code null} if standard output should be suppressed.
385   * @param  err   The output stream to use for standard error.  It may be
386   *               {@code null} if standard error should be suppressed.
387   * @param  args  The command-line arguments to use to run this program.  It
388   *               must not be {@code null}, but may be empty.
389   *
390   * @return  The result code with information about the result of processing.
391   *          A result code of {@code SUCCESS} indicates that all processing
392   *          completed successfully and no differences were identified.  A
393   *          result code of {@code COMPARE_FALSE} indicates that all processing
394   *          completed successfully, but one or more differences were
395   *          identified between the server schemas.  Any other result code
396   *          indicates that some problem occurred during processing.
397   */
398  @NotNull()
399  public static ResultCode main(@Nullable final OutputStream out,
400                                @Nullable final OutputStream err,
401                                @NotNull final String... args)
402  {
403    final CompareLDAPSchemas tool = new CompareLDAPSchemas(out, err);
404    return tool.runTool(args);
405  }
406
407
408
409  /**
410   * Creates a new instance of this tool with the provided streams for standard
411   * output and standard error.
412   *
413   * @param  out  The output stream to use for standard output.  It may be
414   *              {@code null} if standard output should be suppressed.
415   * @param  err  The output stream to use for standard error.  It may be
416   *              {@code null} if standard error should be suppressed.
417   */
418  public CompareLDAPSchemas(@Nullable final OutputStream out,
419                            @Nullable final OutputStream err)
420  {
421    super(out, err, new String[] { "first", "second" }, null);
422
423    parserRef = new AtomicReference<>();
424    completionMessageRef = new AtomicReference<>();
425
426    schemaElementTypes = new HashSet<>(StaticUtils.setOf(
427         SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAXES,
428         SCHEMA_ELEMENT_TYPE_MATCHING_RULES,
429         SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPES,
430         SCHEMA_ELEMENT_TYPE_OBJECT_CLASSES,
431         SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULES,
432         SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULES,
433         SCHEMA_ELEMENT_TYPE_NAME_FORMS,
434         SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USES));
435
436    ignoreDescriptions = false;
437    ignoreExtensions = false;
438    includeNamePrefixes = new ArrayList<>();
439    excludeNamePrefixes = new ArrayList<>();
440    includeExtensionValues = new HashMap<>();
441    excludeExtensionValues = new HashMap<>();
442  }
443
444
445
446  /**
447   * {@inheritDoc}
448   */
449  @Override()
450  @NotNull()
451  public String getToolName()
452  {
453    return "compare-ldap-schemas";
454  }
455
456
457
458  /**
459   * {@inheritDoc}
460   */
461  @Override()
462  @NotNull()
463  public String getToolDescription()
464  {
465    return INFO_COMPARE_SCHEMA_TOOL_DESC.get();
466  }
467
468
469
470  /**
471   * {@inheritDoc}
472   */
473  @Override()
474  @NotNull()
475  public String getToolVersion()
476  {
477    return Version.getNumericVersionString();
478  }
479
480
481
482  /**
483   * {@inheritDoc}
484   */
485  @Override()
486  protected boolean includeAlternateLongIdentifiers()
487  {
488    return true;
489  }
490
491
492
493  /**
494   * {@inheritDoc}
495   */
496  @Override()
497  public void addNonLDAPArguments(@NotNull final ArgumentParser parser)
498         throws ArgumentException
499  {
500    parserRef.set(parser);
501
502    final DNArgument firstSchemaEntryDNArg = new DNArgument(null,
503         ARG_NAME_FIRST_SCHEMA_ENTRY_DN, false, 1, null,
504         INFO_COMPARE_SCHEMA_ARG_DESC_FIRST_SCHEMA_ENTRY_DN.get());
505    firstSchemaEntryDNArg.addLongIdentifier("first-schema-entry-dn", true);
506    firstSchemaEntryDNArg.addLongIdentifier("firstSchemaEntry", true);
507    firstSchemaEntryDNArg.addLongIdentifier("first-schema-entry", true);
508    firstSchemaEntryDNArg.addLongIdentifier("firstSchemaDN", true);
509    firstSchemaEntryDNArg.addLongIdentifier("first-schema-dn", true);
510    firstSchemaEntryDNArg.addLongIdentifier("firstSchema", true);
511    firstSchemaEntryDNArg.addLongIdentifier("first-schema", true);
512    parser.addArgument(firstSchemaEntryDNArg);
513
514    final DNArgument secondSchemaEntryDNArg = new DNArgument(null,
515         ARG_NAME_SECOND_SCHEMA_ENTRY_DN, false, 1, null,
516         INFO_COMPARE_SCHEMA_ARG_DESC_SECOND_SCHEMA_ENTRY_DN.get());
517    secondSchemaEntryDNArg.addLongIdentifier("second-schema-entry-dn", true);
518    secondSchemaEntryDNArg.addLongIdentifier("secondSchemaEntry", true);
519    secondSchemaEntryDNArg.addLongIdentifier("second-schema-entry", true);
520    secondSchemaEntryDNArg.addLongIdentifier("secondSchemaDN", true);
521    secondSchemaEntryDNArg.addLongIdentifier("second-schema-dn", true);
522    secondSchemaEntryDNArg.addLongIdentifier("secondSchema", true);
523    secondSchemaEntryDNArg.addLongIdentifier("second-schema", true);
524    parser.addArgument(secondSchemaEntryDNArg);
525
526    final StringArgument schemaElementTypesArg = new StringArgument(null,
527         ARG_NAME_SCHEMA_ELEMENT_TYPE, false, 0,
528         INFO_COMPARE_SCHEMA_ARG_PLACEHOLDER_SCHEMA_ELEMENT_TYPE.get(),
529         INFO_COMPARE_SCHEMA_ARG_DESC_SCHEMA_ELEMENT_TYPE.get(
530              SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAXES,
531              SCHEMA_ELEMENT_TYPE_MATCHING_RULES,
532              SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPES,
533              SCHEMA_ELEMENT_TYPE_OBJECT_CLASSES,
534              SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULES,
535              SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULES,
536              SCHEMA_ELEMENT_TYPE_NAME_FORMS,
537              SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USES));
538    schemaElementTypesArg.addLongIdentifier("schema-element-types", true);
539    parser.addArgument(schemaElementTypesArg);
540
541    final BooleanArgument getExtendedSchemaInfoArg = new BooleanArgument(null,
542         ARG_NAME_GET_EXTENDED_SCHEMA_INFO, 1,
543         INFO_COMPARE_SCHEMA_ARG_DESC_GET_EXTENDED_SCHEMA_INFO.get());
544    getExtendedSchemaInfoArg.addLongIdentifier("get-extended-schema-info",
545         true);
546    parser.addArgument(getExtendedSchemaInfoArg);
547
548    final BooleanArgument ignoreDescriptionsArg = new BooleanArgument(null,
549         ARG_NAME_IGNORE_DESCRIPTIONS, 1,
550         INFO_COMPARE_SCHEMA_ARG_DESC_IGNORE_DESCRIPTIONS.get());
551    ignoreDescriptionsArg.addLongIdentifier("ignore-descriptions", true);
552    ignoreDescriptionsArg.addLongIdentifier("ignoreDescription", true);
553    ignoreDescriptionsArg.addLongIdentifier("ignore-description", true);
554    parser.addArgument(ignoreDescriptionsArg);
555
556    final BooleanArgument ignoreExtensionsArg = new BooleanArgument(null,
557         ARG_NAME_IGNORE_EXTENSIONS, 1,
558         INFO_COMPARE_SCHEMA_ARG_DESC_IGNORE_EXTENSIONS.get());
559    ignoreExtensionsArg.addLongIdentifier("ignore-extensions", true);
560    ignoreExtensionsArg.addLongIdentifier("ignoreExtension", true);
561    ignoreExtensionsArg.addLongIdentifier("ignore-extension", true);
562    parser.addArgument(ignoreExtensionsArg);
563
564    final StringArgument includeElementsWithNameMatchingPrefixArg =
565         new StringArgument(null,
566              ARG_NAME_INCLUDE_ELEMENTS_WITH_NAME_MATCHING_PREFIX, false, 0,
567              INFO_COMPARE_SCHEMA_ARG_PLACEHOLDER_PREFIX.get(),
568              INFO_COMPARE_SCHEMA_ARG_DESC_INCLUDE_NAME_MATCHING_PREFIX.get());
569    includeElementsWithNameMatchingPrefixArg.addLongIdentifier(
570         "include-elements-with-name-matching-prefix", true);
571    parser.addArgument(includeElementsWithNameMatchingPrefixArg);
572
573    final StringArgument excludeElementsWithNameMatchingPrefixArg =
574         new StringArgument(null,
575              ARG_NAME_EXCLUDE_ELEMENTS_WITH_NAME_MATCHING_PREFIX, false, 0,
576              INFO_COMPARE_SCHEMA_ARG_PLACEHOLDER_PREFIX.get(),
577              INFO_COMPARE_SCHEMA_ARG_DESC_EXCLUDE_NAME_MATCHING_PREFIX.get());
578    excludeElementsWithNameMatchingPrefixArg.addLongIdentifier(
579         "exclude-elements-with-name-matching-prefix", true);
580    parser.addArgument(excludeElementsWithNameMatchingPrefixArg);
581
582    final StringArgument includeElementsWithExtensionValueArg =
583         new StringArgument(null,
584              ARG_NAME_INCLUDE_ELEMENTS_WITH_EXTENSION_VALUE, false, 0,
585              INFO_COMPARE_SCHEMA_ARG_PLACEHOLDER_EXTENSION_VALUE.get(),
586              INFO_COMPARE_SCHEMA_ARG_DESC_INCLUDE_EXTENSION_VALUE.get());
587    includeElementsWithExtensionValueArg.addLongIdentifier(
588         "include-elements-with-extension-value", true);
589    parser.addArgument(includeElementsWithExtensionValueArg);
590
591    final StringArgument excludeElementsWithExtensionValueArg =
592         new StringArgument(null,
593              ARG_NAME_EXCLUDE_ELEMENTS_WITH_EXTENSION_VALUE, false, 0,
594              INFO_COMPARE_SCHEMA_ARG_PLACEHOLDER_EXTENSION_VALUE.get(),
595              INFO_COMPARE_SCHEMA_ARG_DESC_EXCLUDE_EXTENSION_VALUE.get());
596    excludeElementsWithExtensionValueArg.addLongIdentifier(
597         "exclude-elements-with-extension-value", true);
598    parser.addArgument(excludeElementsWithExtensionValueArg);
599  }
600
601
602
603  /**
604   * {@inheritDoc}
605   */
606  @Override()
607  public void doExtendedNonLDAPArgumentValidation()
608         throws ArgumentException
609  {
610    // Identify the types of schema elements to examine.
611    final ArgumentParser parser = parserRef.get();
612    final StringArgument schemaElementTypesArg =
613         parser.getStringArgument(ARG_NAME_SCHEMA_ELEMENT_TYPE);
614    if ((schemaElementTypesArg != null) && schemaElementTypesArg.isPresent())
615    {
616      schemaElementTypes.clear();
617      for (final String value : schemaElementTypesArg.getValues())
618      {
619        if (value.equalsIgnoreCase(SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAXES))
620        {
621          schemaElementTypes.add(SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAXES);
622        }
623        else if (value.equalsIgnoreCase(SCHEMA_ELEMENT_TYPE_MATCHING_RULES))
624        {
625          schemaElementTypes.add(SCHEMA_ELEMENT_TYPE_MATCHING_RULES);
626        }
627        else if (value.equalsIgnoreCase(SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPES))
628        {
629          schemaElementTypes.add(SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPES);
630        }
631        else if (value.equalsIgnoreCase(SCHEMA_ELEMENT_TYPE_OBJECT_CLASSES))
632        {
633          schemaElementTypes.add(SCHEMA_ELEMENT_TYPE_OBJECT_CLASSES);
634        }
635        else if (value.equalsIgnoreCase(SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULES))
636        {
637          schemaElementTypes.add(SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULES);
638        }
639        else if (value.equalsIgnoreCase(
640             SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULES))
641        {
642          schemaElementTypes.add(SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULES);
643        }
644        else if (value.equalsIgnoreCase(SCHEMA_ELEMENT_TYPE_NAME_FORMS))
645        {
646          schemaElementTypes.add(SCHEMA_ELEMENT_TYPE_NAME_FORMS);
647        }
648        else if (value.equalsIgnoreCase(SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USES))
649        {
650          schemaElementTypes.add(SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USES);
651        }
652        else
653        {
654          throw new ArgumentException(
655               ERR_COMPARE_SCHEMA_INVALID_SCHEMA_ELEMENT_TYPE.get(value,
656                    ARG_NAME_SCHEMA_ELEMENT_TYPE,
657                    SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAXES,
658                    SCHEMA_ELEMENT_TYPE_MATCHING_RULES,
659                    SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPES,
660                    SCHEMA_ELEMENT_TYPE_OBJECT_CLASSES,
661                    SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULES,
662                    SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULES,
663                    SCHEMA_ELEMENT_TYPE_NAME_FORMS,
664                    SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USES));
665        }
666      }
667    }
668
669
670    // Determine whether to ignore schema element descriptions or extensions.
671    final BooleanArgument ignoreDescriptionsArg =
672         parser.getBooleanArgument(ARG_NAME_IGNORE_DESCRIPTIONS);
673    ignoreDescriptions =
674         ((ignoreDescriptionsArg != null) && ignoreDescriptionsArg.isPresent());
675
676    final BooleanArgument ignoreExtensionsArg =
677         parser.getBooleanArgument(ARG_NAME_IGNORE_EXTENSIONS);
678    ignoreExtensions =
679         ((ignoreExtensionsArg != null) && ignoreExtensionsArg.isPresent());
680
681
682    // Identify the schema element name prefixes to include and exclude.
683    getNamePrefixes(ARG_NAME_INCLUDE_ELEMENTS_WITH_NAME_MATCHING_PREFIX,
684         includeNamePrefixes);
685    getNamePrefixes(ARG_NAME_EXCLUDE_ELEMENTS_WITH_NAME_MATCHING_PREFIX,
686         excludeNamePrefixes);
687    includeOrExcludeBasedOnName = (! includeNamePrefixes.isEmpty()) ||
688         (! excludeNamePrefixes.isEmpty());
689
690
691    // Identify the schema element extension values to include and exclude.
692    getExtensionValues(ARG_NAME_INCLUDE_ELEMENTS_WITH_EXTENSION_VALUE,
693         includeExtensionValues);
694    getExtensionValues(ARG_NAME_EXCLUDE_ELEMENTS_WITH_EXTENSION_VALUE,
695         excludeExtensionValues);
696    includeOrExcludeBasedOnExtensions = (! includeExtensionValues.isEmpty()) ||
697         (! excludeExtensionValues.isEmpty());
698  }
699
700
701
702  /**
703   * Populates the provided list with the set of schema element prefixes
704   * contained in the specified argument.
705   *
706   * @param  argumentName  The name of the argument whose values will be used to
707   *                       populate the given list.
708   * @param  prefixList    The list to be updated to include the values of the
709   *                       specified argument.
710   */
711  private void getNamePrefixes(@NotNull final String argumentName,
712                               @NotNull final List<String> prefixList)
713  {
714    prefixList.clear();
715    final StringArgument arg = parserRef.get().getStringArgument(argumentName);
716    if ((arg == null) || (! arg.isPresent()))
717    {
718      return;
719    }
720
721    for (final String value : arg.getValues())
722    {
723      prefixList.add(StaticUtils.toLowerCase(value));
724    }
725  }
726
727
728
729  /**
730   * Populates the provided map with the set of schema element extension
731   * name-value pairs contained in the specified argument.
732   *
733   * @param  argumentName  The name of the argument whose values will be used to
734   *                       populate the given map.
735   * @param  extensionMap  The map to be updated to include the values of the
736   *                       specified argument.
737   *
738   * @throws  ArgumentException  If there is a problem with any of the values of
739   *                             the specified argument.
740   */
741  private void getExtensionValues(
742                    @NotNull final String argumentName,
743                    @NotNull final Map<String,List<String>> extensionMap)
744          throws ArgumentException
745  {
746    extensionMap.clear();
747    final StringArgument arg = parserRef.get().getStringArgument(argumentName);
748    if ((arg == null) || (! arg.isPresent()))
749    {
750      return;
751    }
752
753    for (final String value : arg.getValues())
754    {
755      final int equalPos = value.indexOf('=');
756      if (equalPos < 0)
757      {
758        throw new ArgumentException(
759             ERR_COMPARE_SCHEMA_EXTENSION_VALUE_NO_EQUALS.get(argumentName,
760                  value));
761      }
762
763      final String extensionName =
764           StaticUtils.toLowerCase(value.substring(0, equalPos));
765      if (extensionName.isEmpty())
766      {
767        throw new ArgumentException(
768             ERR_COMPARE_SCHEMA_EXTENSION_VALUE_EMPTY_NAME.get(argumentName,
769                  value));
770      }
771
772      final String extensionValue =
773           StaticUtils.toLowerCase(value.substring(equalPos + 1));
774      if (extensionValue.isEmpty())
775      {
776        throw new ArgumentException(
777             ERR_COMPARE_SCHEMA_EXTENSION_VALUE_EMPTY_VALUE.get(argumentName,
778                  value));
779      }
780
781      List<String> valueList = extensionMap.get(extensionName);
782      if (valueList == null)
783      {
784        valueList = new ArrayList<>();
785        extensionMap.put(extensionName, valueList);
786      }
787
788      valueList.add(extensionValue);
789    }
790  }
791
792
793
794  /**
795   * {@inheritDoc}
796   */
797  @Override()
798  public boolean supportsInteractiveMode()
799  {
800    return true;
801  }
802
803
804
805  /**
806   * {@inheritDoc}
807   */
808  @Override()
809  public boolean defaultsToInteractiveMode()
810  {
811    return true;
812  }
813
814
815
816  /**
817   * {@inheritDoc}
818   */
819  @Override()
820  public boolean supportsPropertiesFile()
821  {
822    return true;
823  }
824
825
826
827  /**
828   * {@inheritDoc}
829   */
830  @Override()
831  protected boolean supportsOutputFile()
832  {
833    return true;
834  }
835
836
837
838  /**
839   * {@inheritDoc}
840   */
841  @Override()
842  protected boolean logToolInvocationByDefault()
843  {
844    return false;
845  }
846
847
848
849  /**
850   * {@inheritDoc}
851   */
852  @Override()
853  @Nullable()
854  protected String getToolCompletionMessage()
855  {
856    return completionMessageRef.get();
857  }
858
859
860
861  /**
862   * {@inheritDoc}
863   */
864  @Override()
865  @NotNull()
866  public ResultCode doToolProcessing()
867  {
868    // Get the schemas from each of the servers.
869    final Schema firstServerSchema;
870    final Map<String,LDAPException> firstUnparsableAttributeSyntaxes =
871         new LinkedHashMap<>();
872    final Map<String,LDAPException> firstUnparsableMatchingRules =
873         new LinkedHashMap<>();
874    final Map<String,LDAPException> firstUnparsableAttributeTypes =
875         new LinkedHashMap<>();
876    final Map<String,LDAPException> firstUnparsableObjectClasses =
877         new LinkedHashMap<>();
878    final Map<String,LDAPException> firstUnparsableDITContentRules =
879         new LinkedHashMap<>();
880    final Map<String,LDAPException> firstUnparsableDITStructureRules =
881         new LinkedHashMap<>();
882    final Map<String,LDAPException> firstUnparsableNameForms =
883         new LinkedHashMap<>();
884    final Map<String,LDAPException> firstUnparsableMatchingRuleUses =
885         new LinkedHashMap<>();
886    try
887    {
888      firstServerSchema = getSchema(FIRST_SERVER_INDEX,
889           ARG_NAME_FIRST_SCHEMA_ENTRY_DN,
890           INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
891           firstUnparsableAttributeSyntaxes, firstUnparsableMatchingRules,
892           firstUnparsableAttributeTypes, firstUnparsableObjectClasses,
893           firstUnparsableDITContentRules, firstUnparsableDITStructureRules,
894           firstUnparsableNameForms, firstUnparsableMatchingRuleUses);
895    }
896    catch (final LDAPException e)
897    {
898      logCompletionError(e.getMessage());
899      return e.getResultCode();
900    }
901
902    final Schema secondServerSchema;
903    final Map<String,LDAPException> secondUnparsableAttributeSyntaxes =
904         new LinkedHashMap<>();
905    final Map<String,LDAPException> secondUnparsableMatchingRules =
906         new LinkedHashMap<>();
907    final Map<String,LDAPException> secondUnparsableAttributeTypes =
908         new LinkedHashMap<>();
909    final Map<String,LDAPException> secondUnparsableObjectClasses =
910         new LinkedHashMap<>();
911    final Map<String,LDAPException> secondUnparsableDITContentRules =
912         new LinkedHashMap<>();
913    final Map<String,LDAPException> secondUnparsableDITStructureRules =
914         new LinkedHashMap<>();
915    final Map<String,LDAPException> secondUnparsableNameForms =
916         new LinkedHashMap<>();
917    final Map<String,LDAPException> secondUnparsableMatchingRuleUses =
918         new LinkedHashMap<>();
919    try
920    {
921      secondServerSchema = getSchema(SECOND_SERVER_INDEX,
922           ARG_NAME_SECOND_SCHEMA_ENTRY_DN,
923           INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
924           secondUnparsableAttributeSyntaxes, secondUnparsableMatchingRules,
925           secondUnparsableAttributeTypes, secondUnparsableObjectClasses,
926           secondUnparsableDITContentRules, secondUnparsableDITStructureRules,
927           secondUnparsableNameForms, secondUnparsableMatchingRuleUses);
928    }
929    catch (final LDAPException e)
930    {
931      logCompletionError(e.getMessage());
932      return e.getResultCode();
933    }
934
935
936    // Report on any unparsable schema elements.
937    final AtomicReference<ResultCode> resultCodeRef = new AtomicReference<>();
938    boolean unparsableElementsEncountered = reportUnparsableSchemaElements(
939         INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
940         firstUnparsableAttributeSyntaxes, firstUnparsableMatchingRules,
941         firstUnparsableAttributeTypes, firstUnparsableObjectClasses,
942         firstUnparsableDITContentRules, firstUnparsableDITStructureRules,
943         firstUnparsableNameForms, firstUnparsableMatchingRuleUses);
944
945    unparsableElementsEncountered |= reportUnparsableSchemaElements(
946         INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
947         secondUnparsableAttributeSyntaxes, secondUnparsableMatchingRules,
948         secondUnparsableAttributeTypes, secondUnparsableObjectClasses,
949         secondUnparsableDITContentRules, secondUnparsableDITStructureRules,
950         secondUnparsableNameForms, secondUnparsableMatchingRuleUses);
951
952    if (unparsableElementsEncountered)
953    {
954      resultCodeRef.set(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
955    }
956
957
958    // Validate the different types of schema elements.
959    final AtomicInteger numDifferences = new AtomicInteger();
960    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAXES))
961    {
962      compareAttributeSyntaxes(firstServerSchema, secondServerSchema,
963           numDifferences);
964    }
965
966    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_MATCHING_RULES))
967    {
968      compareMatchingRules(firstServerSchema, secondServerSchema,
969           numDifferences);
970    }
971
972    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPES))
973    {
974      compareAttributeTypes(firstServerSchema, secondServerSchema,
975           numDifferences);
976    }
977
978    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_OBJECT_CLASSES))
979    {
980      compareObjectClasses(firstServerSchema, secondServerSchema,
981           numDifferences);
982    }
983
984    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULES))
985    {
986      compareDITContentRules(firstServerSchema, secondServerSchema,
987           numDifferences);
988    }
989
990    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULES))
991    {
992      compareDITStructureRules(firstServerSchema, secondServerSchema,
993           numDifferences);
994    }
995
996    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_NAME_FORMS))
997    {
998      compareNameForms(firstServerSchema, secondServerSchema, numDifferences);
999    }
1000
1001    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USES))
1002    {
1003      compareMatchingRuleUses(firstServerSchema, secondServerSchema,
1004           numDifferences);
1005    }
1006
1007
1008    // If any errors were encountered, then return an error result code.
1009    // Otherwise, if any differences were encountered, then return a
1010    // COMPARE_FALSE result code.  Otherwise, return a SUCCESS result code.
1011    final int differenceCount = numDifferences.get();
1012    if (unparsableElementsEncountered)
1013    {
1014      switch (differenceCount)
1015      {
1016        case 0:
1017          logCompletionError(
1018               ERR_COMPARE_SCHEMA_SUMMARY_UNPARSABLE_NO_DIFFERENCES.get());
1019          break;
1020        case 1:
1021          logCompletionError(
1022               ERR_COMPARE_SCHEMA_SUMMARY_UNPARSABLE_WITH_DIFFERENCE.get());
1023          break;
1024        default:
1025          logCompletionError(
1026               ERR_COMPARE_SCHEMA_SUMMARY_UNPARSABLE_WITH_DIFFERENCES.get(
1027                    differenceCount));
1028          break;
1029      }
1030    }
1031    else if (differenceCount > 0)
1032    {
1033      resultCodeRef.compareAndSet(null, ResultCode.COMPARE_FALSE);
1034      if (differenceCount == 1)
1035      {
1036        logCompletionError(
1037             ERR_COMPARE_SCHEMA_SUMMARY_DIFFERENCE.get());
1038      }
1039      else
1040      {
1041        logCompletionError(
1042             ERR_COMPARE_SCHEMA_SUMMARY_DIFFERENCES.get(differenceCount));
1043      }
1044    }
1045    else
1046    {
1047      resultCodeRef.compareAndSet(null, ResultCode.SUCCESS);
1048      final String message = INFO_COMPARE_SCHEMA_SUMMARY_NO_DIFFERENCES.get();
1049      completionMessageRef.compareAndSet(null, message);
1050      wrapOut(0, WRAP_COLUMN, message);
1051    }
1052
1053    return resultCodeRef.get();
1054  }
1055
1056
1057
1058  /**
1059   * Retrieves the schema from the specified server.
1060   *
1061   * @param  serverIndex
1062   *              The index for the server from which to retrieve the schema.
1063   * @param  schemaDNArgName
1064   *              The name of the argument to use to retrieve the DN of the
1065   *              subschema subentry, if specified.  It must not be
1066   *              {@code null}.
1067   * @param  serverLabel
1068   *              The label to use to refer to the server.  It must not be
1069   *              {@code null}.
1070   * @param  unparsableAttributeSyntaxes
1071   *              A map that will be updated with information about any
1072   *              unparsable attribute syntax definitions found in the schema
1073   *              from the specified server.  Each key will be the unparsable
1074   *              definition, and the corresponding value will be the exception
1075   *              caught while trying to parse it.  It must not be {@code null}.
1076   * @param  unparsableMatchingRules
1077   *              A map that will be updated with information about any
1078   *              unparsable matching rule definitions found in the schema
1079   *              from the specified server.  Each key will be the unparsable
1080   *              definition, and the corresponding value will be the exception
1081   *              caught while trying to parse it.  It must not be {@code null}.
1082   * @param  unparsableAttributeTypes
1083   *              A map that will be updated with information about any
1084   *              unparsable attribute type definitions found in the schema
1085   *              from the specified server.  Each key will be the unparsable
1086   *              definition, and the corresponding value will be the exception
1087   *              caught while trying to parse it.  It must not be {@code null}.
1088   * @param  unparsableObjectClasses
1089   *              A map that will be updated with information about any
1090   *              unparsable object class definitions found in the schema
1091   *              from the specified server.  Each key will be the unparsable
1092   *              definition, and the corresponding value will be the exception
1093   *              caught while trying to parse it.  It must not be {@code null}.
1094   * @param  unparsableDITContentRules
1095   *              A map that will be updated with information about any
1096   *              unparsable DIT content rule definitions found in the schema
1097   *              from the specified server.  Each key will be the unparsable
1098   *              definition, and the corresponding value will be the exception
1099   *              caught while trying to parse it.  It must not be {@code null}.
1100   * @param  unparsableDITStructureRules
1101   *              A map that will be updated with information about any
1102   *              unparsable DIT structure rule definitions found in the schema
1103   *              from the specified server.  Each key will be the unparsable
1104   *              definition, and the corresponding value will be the exception
1105   *              caught while trying to parse it.  It must not be {@code null}.
1106   * @param  unparsableNameForms
1107   *              A map that will be updated with information about any
1108   *              unparsable name form definitions found in the schema
1109   *              from the specified server.  Each key will be the unparsable
1110   *              definition, and the corresponding value will be the exception
1111   *              caught while trying to parse it.  It must not be {@code null}.
1112   * @param  unparsableMatchingRuleUses
1113   *              A map that will be updated with information about any
1114   *              unparsable matching rule use definitions found in the schema
1115   *              from the specified server.  Each key will be the unparsable
1116   *              definition, and the corresponding value will be the exception
1117   *              caught while trying to parse it.  It must not be {@code null}.
1118   *
1119   * @return  The schema retrieved from the server.
1120   *
1121   * @throws  LDAPException  If a problem occurs while attempting to obtain the
1122   *                         schema.
1123   */
1124  @NotNull()
1125  private Schema getSchema(final int serverIndex,
1126       @NotNull final String schemaDNArgName,
1127       @NotNull final String serverLabel,
1128       @NotNull final Map<String,LDAPException> unparsableAttributeSyntaxes,
1129       @NotNull final Map<String,LDAPException> unparsableMatchingRules,
1130       @NotNull final Map<String,LDAPException> unparsableAttributeTypes,
1131       @NotNull final Map<String,LDAPException> unparsableObjectClasses,
1132       @NotNull final Map<String,LDAPException> unparsableDITContentRules,
1133       @NotNull final Map<String,LDAPException> unparsableDITStructureRules,
1134       @NotNull final Map<String,LDAPException> unparsableNameForms,
1135       @NotNull final Map<String,LDAPException> unparsableMatchingRuleUses)
1136       throws LDAPException
1137  {
1138    // Establish a connection to the server.
1139    final LDAPConnection conn;
1140    try
1141    {
1142      conn = getConnection(serverIndex);
1143    }
1144    catch (final LDAPException e)
1145    {
1146      Debug.debugException(e);
1147      throw new LDAPException(e.getResultCode(),
1148           ERR_COMPARE_SCHEMA_CANNOT_CONNECT.get(serverLabel, e.getMessage()),
1149           e);
1150    }
1151
1152    final ArgumentParser parser = parserRef.get();
1153    final BooleanArgument getExtendedSchemaInfoArg =
1154         parser.getBooleanArgument(ARG_NAME_GET_EXTENDED_SCHEMA_INFO);
1155    final boolean getExtendedSchemaInfo =
1156         ((getExtendedSchemaInfoArg != null) &&
1157              getExtendedSchemaInfoArg.isPresent());
1158
1159
1160    try
1161    {
1162      // See if the schema entry DN was specified as an argument.  If so, then
1163      // retrieve that entry and parse it as a schema entry.  Otherwise, use the
1164      // default method for obtaining the schema.
1165      final String schemaEntryDN;
1166      final DNArgument schemaEntryDNArg = parser.getDNArgument(schemaDNArgName);
1167      if (schemaEntryDNArg.isPresent())
1168      {
1169        schemaEntryDN = schemaEntryDNArg.getStringValue();
1170      }
1171      else
1172      {
1173        final RootDSE rootDSE = conn.getRootDSE();
1174        if (rootDSE == null)
1175        {
1176          throw new LDAPException(ResultCode.LOCAL_ERROR,
1177               ERR_COMPARE_SCHEMA_CANNOT_GET_ROOT_DSE.get(serverLabel));
1178        }
1179
1180        schemaEntryDN = rootDSE.getSubschemaSubentryDN();
1181        if (schemaEntryDN == null)
1182        {
1183          throw new LDAPException(ResultCode.LOCAL_ERROR,
1184               ERR_COMPARE_SCHEMA_CANNOT_GET_ROOT_DSE_SCHEMA_DN.get(serverLabel,
1185                    RootDSE.ATTR_SUBSCHEMA_SUBENTRY));
1186        }
1187      }
1188
1189      final SearchRequest searchRequest = new SearchRequest(schemaEntryDN,
1190           SearchScope.BASE, Schema.SUBSCHEMA_SUBENTRY_FILTER,
1191           Schema.SCHEMA_REQUEST_ATTRS);
1192      if (getExtendedSchemaInfo)
1193      {
1194        searchRequest.addControl(new ExtendedSchemaInfoRequestControl(false));
1195      }
1196
1197      final Entry schemaEntry = conn.searchForEntry(searchRequest);
1198      if (schemaEntry == null)
1199      {
1200        throw new LDAPException(ResultCode.NO_SUCH_OBJECT,
1201             ERR_COMPARE_SCHEMA_CANNOT_GET_SCHEMA_ENTRY.get(
1202                  String.valueOf(schemaEntryDN), serverLabel));
1203      }
1204
1205      return new Schema(schemaEntry, unparsableAttributeSyntaxes,
1206           unparsableMatchingRules, unparsableAttributeTypes,
1207           unparsableObjectClasses, unparsableDITContentRules,
1208           unparsableDITStructureRules, unparsableNameForms,
1209           unparsableMatchingRuleUses);
1210    }
1211    catch (final LDAPException e)
1212    {
1213      Debug.debugException(e);
1214      throw new LDAPException(e.getResultCode(),
1215           ERR_COMPARE_SCHEMA_CANNOT_GET_SCHEMA.get(serverLabel,
1216                e.getMessage()),
1217           e);
1218    }
1219    finally
1220    {
1221      conn.close();
1222    }
1223  }
1224
1225
1226
1227  /**
1228   * Reports error messages about any unparsable elements found in a server's
1229   * schema.
1230   *
1231   * @param  serverLabel
1232   *              The label for the associated directory server instance.
1233   * @param  unparsableAttributeSyntaxes
1234   *              A map with information about any unparsable attribute syntax
1235   *              definitions found in the schema.
1236   * @param  unparsableMatchingRules
1237   *              A map with information about any unparsable matching rule
1238   *              definitions found in the schema.
1239   * @param  unparsableAttributeTypes
1240   *              A map with information about any unparsable attribute type
1241   *              definitions found in the schema.
1242   * @param  unparsableObjectClasses
1243   *              A map with information about any unparsable object class
1244   *              definitions found in the schema.
1245   * @param  unparsableDITContentRules
1246   *              A map with information about any unparsable DIT content rule
1247   *              definitions found in the schema.
1248   * @param  unparsableDITStructureRules
1249   *              A map with information about any unparsable DIT structure rule
1250   *              definitions found in the schema.
1251   * @param  unparsableNameForms
1252   *              A map with information about any unparsable name form
1253   *              definitions found in the schema.
1254   * @param  unparsableMatchingRuleUses
1255   *              A map with information about any unparsable matching rule use
1256   *              definitions found in the schema.
1257   *
1258   * @return  {@code true} if the schema contained any unparsable elements, or
1259   *          {@code false} if not.
1260   */
1261  private boolean reportUnparsableSchemaElements(
1262       @NotNull final String serverLabel,
1263       @NotNull final Map<String,LDAPException> unparsableAttributeSyntaxes,
1264       @NotNull final Map<String,LDAPException> unparsableMatchingRules,
1265       @NotNull final Map<String,LDAPException> unparsableAttributeTypes,
1266       @NotNull final Map<String,LDAPException> unparsableObjectClasses,
1267       @NotNull final Map<String,LDAPException> unparsableDITContentRules,
1268       @NotNull final Map<String,LDAPException> unparsableDITStructureRules,
1269       @NotNull final Map<String,LDAPException> unparsableNameForms,
1270       @NotNull final Map<String,LDAPException> unparsableMatchingRuleUses)
1271  {
1272    boolean unparsableFound = false;
1273
1274    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAXES))
1275    {
1276      unparsableFound |= reportUnparsableSchemaElements(serverLabel,
1277           unparsableAttributeSyntaxes,
1278           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAX.get());
1279    }
1280
1281    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_MATCHING_RULES))
1282    {
1283      unparsableFound |= reportUnparsableSchemaElements(serverLabel,
1284           unparsableMatchingRules,
1285           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE.get());
1286    }
1287
1288    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPES))
1289    {
1290      unparsableFound |= reportUnparsableSchemaElements(serverLabel,
1291           unparsableAttributeTypes,
1292           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get());
1293    }
1294
1295    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_OBJECT_CLASSES))
1296    {
1297      unparsableFound |= reportUnparsableSchemaElements(serverLabel,
1298           unparsableObjectClasses,
1299           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get());
1300    }
1301
1302    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULES))
1303    {
1304      unparsableFound |= reportUnparsableSchemaElements(serverLabel,
1305           unparsableDITContentRules,
1306           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get());
1307    }
1308
1309    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULES))
1310    {
1311      unparsableFound |= reportUnparsableSchemaElements(serverLabel,
1312           unparsableDITStructureRules,
1313           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULE.get());
1314    }
1315
1316    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_NAME_FORMS))
1317    {
1318      unparsableFound |= reportUnparsableSchemaElements(serverLabel,
1319           unparsableNameForms,
1320           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get());
1321    }
1322
1323    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USES))
1324    {
1325      unparsableFound |= reportUnparsableSchemaElements(serverLabel,
1326           unparsableMatchingRuleUses,
1327           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USE.get());
1328    }
1329
1330    return unparsableFound;
1331  }
1332
1333
1334
1335  /**
1336   * Reports error messages about any unparsable elements of the specified type
1337   * found in a server's schema.
1338   *
1339   * @param  serverLabel         The label for the associated directory server
1340   *                             instance.  It must not be {@code null}.
1341   * @param  unparsableElements  The set of unparsable elements of a given type.
1342   *                             It must not be {@code null}, but may be empty.
1343   * @param  elementTypeName     The name of the schema element type.  It must
1344   *                             not be {@code null}.
1345   *
1346   * @return  {@code true} if the provided map contained information about one
1347   *          or more unparsable elements, or {@code false} if not.
1348   */
1349  private boolean reportUnparsableSchemaElements(
1350       @NotNull final String serverLabel,
1351       @NotNull final Map<String,LDAPException> unparsableElements,
1352       @NotNull final String elementTypeName)
1353  {
1354    for (final Map.Entry<String,LDAPException> e :
1355         unparsableElements.entrySet())
1356    {
1357      wrapErr(0, WRAP_COLUMN,
1358           ERR_COMPARE_SCHEMA_UNPARSABLE_ELEMENT.get(elementTypeName,
1359                serverLabel, e.getValue().getMessage()));
1360      err(e.getKey());
1361      err();
1362    }
1363
1364    return (! unparsableElements.isEmpty());
1365  }
1366
1367
1368
1369  /**
1370   * Compares the attribute syntax definitions contained in the provided
1371   * schemas.
1372   *
1373   * @param  firstServerSchema   The schema retrieved from the first server.  It
1374   *                             must not be {@code null}.
1375   * @param  secondServerSchema  The schema retrieved from the second server.
1376   *                             It must not be {@code null}.
1377   * @param  numDifferences      A counter used to keep track of the number of
1378   *                             differences found between the schemas.  It must
1379   *                             not be {@code null}.
1380   */
1381  private void compareAttributeSyntaxes(
1382                    @NotNull final Schema firstServerSchema,
1383                    @NotNull final Schema secondServerSchema,
1384                    @NotNull final AtomicInteger numDifferences)
1385  {
1386    // Get the attribute syntax definitions from each of the schemas.
1387    final Map<OID,AttributeSyntaxDefinition> syntaxes1 =
1388         getAttributeSyntaxMap(firstServerSchema);
1389    final Map<OID,AttributeSyntaxDefinition> syntaxes2 =
1390         getAttributeSyntaxMap(secondServerSchema);
1391
1392
1393    // Identify syntaxes that exist in one server but not another.  If any are
1394    // found, then report them and remove them from the set.
1395    Iterator<Map.Entry<OID,AttributeSyntaxDefinition>> iterator =
1396         syntaxes1.entrySet().iterator();
1397    while (iterator.hasNext())
1398    {
1399      final Map.Entry<OID,AttributeSyntaxDefinition> e = iterator.next();
1400      final OID oid = e.getKey();
1401      if (! syntaxes2.containsKey(oid))
1402      {
1403        reportDifference(
1404             WARN_COMPARE_SCHEMA_MISSING_SYNTAX.get(
1405                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
1406                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
1407             numDifferences,
1408             e.getValue().toString());
1409        iterator.remove();
1410      }
1411    }
1412
1413    iterator = syntaxes2.entrySet().iterator();
1414    while (iterator.hasNext())
1415    {
1416      final Map.Entry<OID,AttributeSyntaxDefinition> e = iterator.next();
1417      final OID oid = e.getKey();
1418      if (! syntaxes1.containsKey(oid))
1419      {
1420        reportDifference(
1421             WARN_COMPARE_SCHEMA_MISSING_SYNTAX.get(
1422                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
1423                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
1424             numDifferences,
1425             e.getValue().toString());
1426        iterator.remove();
1427      }
1428    }
1429
1430
1431    // Any remaining syntaxes should exist in both servers.  Compare them and
1432    // see if there are any differences between them.
1433    for (final OID oid : syntaxes1.keySet())
1434    {
1435      final AttributeSyntaxDefinition d1 = syntaxes1.get(oid);
1436      final AttributeSyntaxDefinition d2 = syntaxes2.get(oid);
1437
1438      if (! ignoreDescriptions)
1439      {
1440        compareStringValues(
1441             INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAX.get(),
1442             oid.toString(),
1443             INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(),
1444             d1.getDescription(), d2.getDescription(), numDifferences);
1445      }
1446
1447      compareExtensions(
1448           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAX.get(),
1449           oid.toString(), d1.getExtensions(), d2.getExtensions(),
1450           numDifferences);
1451    }
1452  }
1453
1454
1455
1456  /**
1457   * Retrieves a map of the attribute syntax definitions contained in the
1458   * provided schema, indexed by OID.
1459   *
1460   * @param  schema  The schema from which to retrieve the attribute syntaxes.
1461   *                 It must not be {@code null}.
1462   *
1463   * @return  A map of the attribute syntax definitions contained in the
1464   *          provided schema.
1465   */
1466  @NotNull()
1467  private Map<OID,AttributeSyntaxDefinition> getAttributeSyntaxMap(
1468               @NotNull final Schema schema)
1469  {
1470    final Map<OID,AttributeSyntaxDefinition> syntaxes = new TreeMap<>();
1471    for (final AttributeSyntaxDefinition d : schema.getAttributeSyntaxes())
1472    {
1473      if (includeBasedOnNameAndExtensions(StaticUtils.NO_STRINGS,
1474           d.getExtensions()))
1475      {
1476        syntaxes.put(new OID(StaticUtils.toLowerCase(d.getOID())), d);
1477      }
1478    }
1479
1480    return syntaxes;
1481  }
1482
1483
1484
1485  /**
1486   * Compares the matching rule definitions contained in the provided schemas.
1487   *
1488   * @param  firstServerSchema   The schema retrieved from the first server.  It
1489   *                             must not be {@code null}.
1490   * @param  secondServerSchema  The schema retrieved from the second server.
1491   *                             It must not be {@code null}.
1492   * @param  numDifferences      A counter used to keep track of the number of
1493   *                             differences found between the schemas.  It must
1494   *                             not be {@code null}.
1495   */
1496  private void compareMatchingRules(
1497                    @NotNull final Schema firstServerSchema,
1498                    @NotNull final Schema secondServerSchema,
1499                    @NotNull final AtomicInteger numDifferences)
1500  {
1501    // Get the matching rule definitions from each of the schemas.
1502    final Map<OID,MatchingRuleDefinition> matchingRules1 =
1503         getMatchingRuleMap(firstServerSchema);
1504    final Map<OID,MatchingRuleDefinition> matchingRules2 =
1505         getMatchingRuleMap(secondServerSchema);
1506
1507
1508    // Identify matching rules that exist in one server but not another.  If any
1509    // are found, then report them and remove them from the set.
1510    Iterator<Map.Entry<OID,MatchingRuleDefinition>> iterator =
1511         matchingRules1.entrySet().iterator();
1512    while (iterator.hasNext())
1513    {
1514      final Map.Entry<OID,MatchingRuleDefinition> e = iterator.next();
1515      final OID oid = e.getKey();
1516      if (! matchingRules2.containsKey(oid))
1517      {
1518        reportDifference(
1519             WARN_COMPARE_SCHEMA_MISSING_MATCHING_RULE.get(
1520                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
1521                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
1522             numDifferences,
1523        e.getValue().toString());
1524        iterator.remove();
1525      }
1526    }
1527
1528    iterator = matchingRules2.entrySet().iterator();
1529    while (iterator.hasNext())
1530    {
1531      final Map.Entry<OID,MatchingRuleDefinition> e = iterator.next();
1532      final OID oid = e.getKey();
1533      if (! matchingRules1.containsKey(oid))
1534      {
1535        reportDifference(
1536             WARN_COMPARE_SCHEMA_MISSING_MATCHING_RULE.get(
1537                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
1538                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
1539             numDifferences,
1540             e.getValue().toString());
1541        iterator.remove();
1542      }
1543    }
1544
1545
1546    // Any remaining matching rules should exist in both servers.  Compare them
1547    // and see if there are any differences between them.
1548    for (final OID oid : matchingRules1.keySet())
1549    {
1550      final MatchingRuleDefinition d1 = matchingRules1.get(oid);
1551      final MatchingRuleDefinition d2 = matchingRules2.get(oid);
1552
1553      final String identifier = compareNames(
1554           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE.get(),
1555           oid.toString(), d1.getNames(), d2.getNames(), numDifferences);
1556
1557      compareStringValues(
1558           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE.get(), identifier,
1559           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_SYNTAX_OID.get(),
1560           d1.getSyntaxOID(), d2.getSyntaxOID(), numDifferences);
1561
1562      compareBooleanValues(
1563           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE.get(), identifier,
1564           INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_OBSOLETE.get(),
1565           d1.isObsolete(), d2.isObsolete(), numDifferences);
1566
1567      if (! ignoreDescriptions)
1568      {
1569        compareStringValues(
1570             INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE.get(), identifier,
1571             INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(),
1572             d1.getDescription(), d2.getDescription(), numDifferences);
1573      }
1574
1575      compareExtensions(
1576           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAX.get(),
1577           identifier, d1.getExtensions(), d2.getExtensions(),
1578           numDifferences);
1579    }
1580  }
1581
1582
1583
1584  /**
1585   * Retrieves a map of the matching rule definitions contained in the provided
1586   * schema, indexed by OID.
1587   *
1588   * @param  schema  The schema from which to retrieve the matching rules.  It
1589   *                 must not be {@code null}.
1590   *
1591   * @return  A map of the matching rule definitions contained in the provided
1592   *          schema.
1593   */
1594  @NotNull()
1595  private Map<OID,MatchingRuleDefinition> getMatchingRuleMap(
1596               @NotNull final Schema schema)
1597  {
1598    final Map<OID,MatchingRuleDefinition> matchingRules = new TreeMap<>();
1599    for (final MatchingRuleDefinition d : schema.getMatchingRules())
1600    {
1601      if (includeBasedOnNameAndExtensions(d.getNames(), d.getExtensions()))
1602      {
1603        matchingRules.put(new OID(StaticUtils.toLowerCase(d.getOID())), d);
1604      }
1605    }
1606
1607    return matchingRules;
1608  }
1609
1610
1611
1612  /**
1613   * Compares the attribute type definitions contained in the provided schemas.
1614   *
1615   * @param  firstServerSchema   The schema retrieved from the first server.  It
1616   *                             must not be {@code null}.
1617   * @param  secondServerSchema  The schema retrieved from the second server.
1618   *                             It must not be {@code null}.
1619   * @param  numDifferences      A counter used to keep track of the number of
1620   *                             differences found between the schemas.  It must
1621   *                             not be {@code null}.
1622   */
1623  private void compareAttributeTypes(
1624                    @NotNull final Schema firstServerSchema,
1625                    @NotNull final Schema secondServerSchema,
1626                    @NotNull final AtomicInteger numDifferences)
1627  {
1628    // Get the attribute type definitions from each of the schemas.
1629    final Map<OID,AttributeTypeDefinition> attributeTypes1 =
1630         getAttributeTypeMap(firstServerSchema);
1631    final Map<OID,AttributeTypeDefinition> attributeTypes2 =
1632         getAttributeTypeMap(secondServerSchema);
1633
1634
1635    // Identify attribute types that exist in one server but not another.  If
1636    // any are found, then report them and remove them from the set.
1637    Iterator<Map.Entry<OID,AttributeTypeDefinition>> iterator =
1638         attributeTypes1.entrySet().iterator();
1639    while (iterator.hasNext())
1640    {
1641      final Map.Entry<OID,AttributeTypeDefinition> e = iterator.next();
1642      final OID oid = e.getKey();
1643      if (! attributeTypes2.containsKey(oid))
1644      {
1645        reportDifference(
1646             WARN_COMPARE_SCHEMA_MISSING_ATTRIBUTE_TYPE.get(
1647                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
1648                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
1649             numDifferences,
1650             e.getValue().toString());
1651        iterator.remove();
1652      }
1653    }
1654
1655    iterator = attributeTypes2.entrySet().iterator();
1656    while (iterator.hasNext())
1657    {
1658      final Map.Entry<OID,AttributeTypeDefinition> e = iterator.next();
1659      final OID oid = e.getKey();
1660      if (! attributeTypes1.containsKey(oid))
1661      {
1662        reportDifference(
1663             WARN_COMPARE_SCHEMA_MISSING_ATTRIBUTE_TYPE.get(
1664                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
1665                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
1666             numDifferences,
1667             e.getValue().toString());
1668        iterator.remove();
1669      }
1670    }
1671
1672
1673    // Any remaining attribute types should exist in both servers.  Compare them
1674    // and see if there are any differences between them.
1675    for (final OID oid : attributeTypes1.keySet())
1676    {
1677      final AttributeTypeDefinition d1 = attributeTypes1.get(oid);
1678      final AttributeTypeDefinition d2 = attributeTypes2.get(oid);
1679
1680      final String identifier = compareNames(
1681           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(),
1682           oid.toString(), d1.getNames(), d2.getNames(), numDifferences);
1683
1684      compareStringValues(
1685           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier,
1686           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_SUPERIOR_TYPE.get(),
1687           d1.getSuperiorType(), d2.getSuperiorType(), numDifferences);
1688
1689      compareStringValues(
1690           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier,
1691           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_SYNTAX_OID.get(),
1692           d1.getSyntaxOID(), d2.getSyntaxOID(), numDifferences);
1693
1694      compareStringValues(
1695           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier,
1696           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_EQUALITY_MR.get(),
1697           d1.getEqualityMatchingRule(), d2.getEqualityMatchingRule(),
1698           numDifferences);
1699
1700      compareStringValues(
1701           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier,
1702           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_ORDERING_MR.get(),
1703           d1.getOrderingMatchingRule(), d2.getOrderingMatchingRule(),
1704           numDifferences);
1705
1706      compareStringValues(
1707           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier,
1708           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_SUBSTRING_MR.get(),
1709           d1.getSubstringMatchingRule(), d2.getSubstringMatchingRule(),
1710           numDifferences);
1711
1712      compareBooleanValues(
1713           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier,
1714           INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_SINGLE_VALUE.get(),
1715           d1.isSingleValued(), d2.isSingleValued(), numDifferences);
1716
1717      compareStringValues(
1718           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier,
1719           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_USAGE.get(),
1720           d1.getUsage().getName(), d2.getUsage().getName(),
1721           numDifferences);
1722
1723      compareBooleanValues(
1724           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier,
1725           INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_NO_USER_MOD.get(),
1726           d1.isNoUserModification(), d2.isNoUserModification(),
1727           numDifferences);
1728
1729      compareBooleanValues(
1730           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier,
1731           INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_COLLECTIVE.get(),
1732           d1.isCollective(), d2.isCollective(), numDifferences);
1733
1734      compareBooleanValues(
1735           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier,
1736           INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_OBSOLETE.get(),
1737           d1.isObsolete(), d2.isObsolete(), numDifferences);
1738
1739      if (! ignoreDescriptions)
1740      {
1741        compareStringValues(
1742             INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier,
1743             INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(),
1744             d1.getDescription(), d2.getDescription(), numDifferences);
1745      }
1746
1747      compareExtensions(
1748           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAX.get(),
1749           identifier, d1.getExtensions(), d2.getExtensions(),
1750           numDifferences);
1751    }
1752  }
1753
1754
1755
1756  /**
1757   * Retrieves a map of the attribute type definitions contained in the provided
1758   * schema, indexed by OID.
1759   *
1760   * @param  schema  The schema from which to retrieve the attribute types.  It
1761   *                 must not be {@code null}.
1762   *
1763   * @return  A map of the attribute type definitions contained in the provided
1764   *          schema.
1765   */
1766  @NotNull()
1767  private Map<OID,AttributeTypeDefinition> getAttributeTypeMap(
1768               @NotNull final Schema schema)
1769  {
1770    final Map<OID,AttributeTypeDefinition> attributeTypes = new TreeMap<>();
1771    for (final AttributeTypeDefinition d : schema.getAttributeTypes())
1772    {
1773      if (includeBasedOnNameAndExtensions(d.getNames(), d.getExtensions()))
1774      {
1775        attributeTypes.put(new OID(StaticUtils.toLowerCase(d.getOID())), d);
1776      }
1777    }
1778
1779    return attributeTypes;
1780  }
1781
1782
1783
1784  /**
1785   * Compares the object class definitions contained in the provided schemas.
1786   *
1787   * @param  firstServerSchema   The schema retrieved from the first server.  It
1788   *                             must not be {@code null}.
1789   * @param  secondServerSchema  The schema retrieved from the second server.
1790   *                             It must not be {@code null}.
1791   * @param  numDifferences      A counter used to keep track of the number of
1792   *                             differences found between the schemas.  It must
1793   *                             not be {@code null}.
1794   */
1795  private void compareObjectClasses(
1796                    @NotNull final Schema firstServerSchema,
1797                    @NotNull final Schema secondServerSchema,
1798                    @NotNull final AtomicInteger numDifferences)
1799  {
1800    // Get the object class definitions from each of the schemas.
1801    final Map<OID,ObjectClassDefinition> objectClasses1 =
1802         getObjectClassMap(firstServerSchema);
1803    final Map<OID,ObjectClassDefinition> objectClasses2 =
1804         getObjectClassMap(secondServerSchema);
1805
1806
1807    // Identify object classes that exist in one server but not another.  If
1808    // any are found, then report them and remove them from the set.
1809    Iterator<Map.Entry<OID,ObjectClassDefinition>> iterator =
1810         objectClasses1.entrySet().iterator();
1811    while (iterator.hasNext())
1812    {
1813      final Map.Entry<OID,ObjectClassDefinition> e = iterator.next();
1814      final OID oid = e.getKey();
1815      if (! objectClasses2.containsKey(oid))
1816      {
1817        reportDifference(
1818             WARN_COMPARE_SCHEMA_MISSING_OBJECT_CLASS.get(
1819                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
1820                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
1821             numDifferences,
1822             e.getValue().toString());
1823        iterator.remove();
1824      }
1825    }
1826
1827    iterator = objectClasses2.entrySet().iterator();
1828    while (iterator.hasNext())
1829    {
1830      final Map.Entry<OID,ObjectClassDefinition> e = iterator.next();
1831      final OID oid = e.getKey();
1832      if (! objectClasses1.containsKey(oid))
1833      {
1834        reportDifference(
1835             WARN_COMPARE_SCHEMA_MISSING_OBJECT_CLASS.get(
1836                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
1837                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
1838             numDifferences,
1839             e.getValue().toString());
1840        iterator.remove();
1841      }
1842    }
1843
1844
1845    // Any remaining object classes should exist in both servers.  Compare them
1846    // and see if there are any differences between them.
1847    for (final OID oid : objectClasses1.keySet())
1848    {
1849      final ObjectClassDefinition d1 = objectClasses1.get(oid);
1850      final ObjectClassDefinition d2 = objectClasses2.get(oid);
1851
1852      final String identifier = compareNames(
1853           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(),
1854           oid.toString(), d1.getNames(), d2.getNames(), numDifferences);
1855
1856      compareStringArrayValues(
1857           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(), identifier,
1858           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_SUPERIOR_TYPE.get(),
1859           d1.getSuperiorClasses(), d2.getSuperiorClasses(), numDifferences);
1860
1861      compareStringArrayValues(
1862           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(), identifier,
1863           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_REQUIRED_ATTRIBUTE.get(),
1864           d1.getRequiredAttributes(), d2.getRequiredAttributes(),
1865           numDifferences);
1866
1867      compareStringArrayValues(
1868           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(), identifier,
1869           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_OPTIONAL_ATTRIBUTE.get(),
1870           d1.getOptionalAttributes(), d2.getOptionalAttributes(),
1871           numDifferences);
1872
1873      final String oc1Type = (d1.getObjectClassType() == null)
1874           ? null
1875           : d1.getObjectClassType().getName();
1876      final String oc2Type = (d2.getObjectClassType() == null)
1877           ? null
1878           : d2.getObjectClassType().getName();
1879      compareStringValues(
1880           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(), identifier,
1881           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_OBJECT_CLASS_TYPE.get(),
1882           oc1Type, oc2Type, numDifferences);
1883
1884      compareBooleanValues(
1885           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(), identifier,
1886           INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_OBSOLETE.get(),
1887           d1.isObsolete(), d2.isObsolete(), numDifferences);
1888
1889      if (! ignoreDescriptions)
1890      {
1891        compareStringValues(
1892             INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(), identifier,
1893             INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(),
1894             d1.getDescription(), d2.getDescription(), numDifferences);
1895      }
1896
1897      compareExtensions(
1898           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(),
1899           identifier, d1.getExtensions(), d2.getExtensions(),
1900           numDifferences);
1901    }
1902  }
1903
1904
1905
1906  /**
1907   * Retrieves a map of the object class definitions contained in the provided
1908   * schema, indexed by OID.
1909   *
1910   * @param  schema  The schema from which to retrieve the object classes.  It
1911   *                 must not be {@code null}.
1912   *
1913   * @return  A map of the object class definitions contained in the provided
1914   *          schema.
1915   */
1916  @NotNull()
1917  private Map<OID,ObjectClassDefinition> getObjectClassMap(
1918               @NotNull final Schema schema)
1919  {
1920    final Map<OID,ObjectClassDefinition> objectClasses = new TreeMap<>();
1921    for (final ObjectClassDefinition d : schema.getObjectClasses())
1922    {
1923      if (includeBasedOnNameAndExtensions(d.getNames(), d.getExtensions()))
1924      {
1925        objectClasses.put(new OID(StaticUtils.toLowerCase(d.getOID())), d);
1926      }
1927    }
1928
1929    return objectClasses;
1930  }
1931
1932
1933
1934  /**
1935   * Compares the DIT content rule definitions contained in the provided
1936   * schemas.
1937   *
1938   * @param  firstServerSchema   The schema retrieved from the first server.  It
1939   *                             must not be {@code null}.
1940   * @param  secondServerSchema  The schema retrieved from the second server.
1941   *                             It must not be {@code null}.
1942   * @param  numDifferences      A counter used to keep track of the number of
1943   *                             differences found between the schemas.  It must
1944   *                             not be {@code null}.
1945   */
1946  private void compareDITContentRules(
1947                    @NotNull final Schema firstServerSchema,
1948                    @NotNull final Schema secondServerSchema,
1949                    @NotNull final AtomicInteger numDifferences)
1950  {
1951    // Get the DIT content rule definitions from each of the schemas.
1952    final Map<OID,DITContentRuleDefinition> ditContentRules1 =
1953         getDITContentRuleMap(firstServerSchema);
1954    final Map<OID,DITContentRuleDefinition> ditContentRules2 =
1955         getDITContentRuleMap(secondServerSchema);
1956
1957
1958    // Identify DIT content rules that exist in one server but not another.  If
1959    // any are found, then report them and remove them from the set.
1960    Iterator<Map.Entry<OID,DITContentRuleDefinition>> iterator =
1961         ditContentRules1.entrySet().iterator();
1962    while (iterator.hasNext())
1963    {
1964      final Map.Entry<OID,DITContentRuleDefinition> e = iterator.next();
1965      final OID oid = e.getKey();
1966      if (! ditContentRules2.containsKey(oid))
1967      {
1968        reportDifference(
1969             WARN_COMPARE_SCHEMA_MISSING_DIT_CONTENT_RULE.get(
1970                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
1971                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
1972             numDifferences,
1973             e.getValue().toString());
1974        iterator.remove();
1975      }
1976    }
1977
1978    iterator = ditContentRules2.entrySet().iterator();
1979    while (iterator.hasNext())
1980    {
1981      final Map.Entry<OID,DITContentRuleDefinition> e = iterator.next();
1982      final OID oid = e.getKey();
1983      if (! ditContentRules1.containsKey(oid))
1984      {
1985        reportDifference(
1986             WARN_COMPARE_SCHEMA_MISSING_DIT_CONTENT_RULE.get(
1987                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
1988                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
1989             numDifferences,
1990             e.getValue().toString());
1991        iterator.remove();
1992      }
1993    }
1994
1995
1996    // Any remaining DIT content rules should exist in both servers.  Compare
1997    // them and see if there are any differences between them.
1998    for (final OID oid : ditContentRules1.keySet())
1999    {
2000      final DITContentRuleDefinition d1 = ditContentRules1.get(oid);
2001      final DITContentRuleDefinition d2 = ditContentRules2.get(oid);
2002
2003      final String identifier = compareNames(
2004           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(),
2005           oid.toString(), d1.getNames(), d2.getNames(), numDifferences);
2006
2007      compareStringArrayValues(
2008           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(), identifier,
2009           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_REQUIRED_ATTRIBUTE.get(),
2010           d1.getRequiredAttributes(), d2.getRequiredAttributes(),
2011           numDifferences);
2012
2013      compareStringArrayValues(
2014           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(), identifier,
2015           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_OPTIONAL_ATTRIBUTE.get(),
2016           d1.getOptionalAttributes(), d2.getOptionalAttributes(),
2017           numDifferences);
2018
2019      compareStringArrayValues(
2020           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(), identifier,
2021           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_PROHIBITED_ATTRIBUTE.get(),
2022           d1.getProhibitedAttributes(), d2.getProhibitedAttributes(),
2023           numDifferences);
2024
2025      compareStringArrayValues(
2026           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(), identifier,
2027           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_AUXILIARY_CLASS.get(),
2028           d1.getAuxiliaryClasses(), d2.getAuxiliaryClasses(), numDifferences);
2029
2030      compareBooleanValues(
2031           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(), identifier,
2032           INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_OBSOLETE.get(),
2033           d1.isObsolete(), d2.isObsolete(), numDifferences);
2034
2035      if (! ignoreDescriptions)
2036      {
2037        compareStringValues(
2038             INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(),
2039             identifier,
2040             INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(),
2041             d1.getDescription(), d2.getDescription(), numDifferences);
2042      }
2043
2044      compareExtensions(
2045           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(),
2046           identifier, d1.getExtensions(), d2.getExtensions(),
2047           numDifferences);
2048    }
2049  }
2050
2051
2052
2053  /**
2054   * Retrieves a map of the DIT content rule definitions contained in the
2055   * provided schema, indexed by OID.
2056   *
2057   * @param  schema  The schema from which to retrieve the DIT content rules.
2058   *                 It must not be {@code null}.
2059   *
2060   * @return  A map of the DIT content rule definitions contained in the
2061   *          provided schema.
2062   */
2063  @NotNull()
2064  private Map<OID,DITContentRuleDefinition> getDITContentRuleMap(
2065               @NotNull final Schema schema)
2066  {
2067    final Map<OID,DITContentRuleDefinition> ditContentRules = new TreeMap<>();
2068    for (final DITContentRuleDefinition d : schema.getDITContentRules())
2069    {
2070      if (includeBasedOnNameAndExtensions(d.getNames(), d.getExtensions()))
2071      {
2072        ditContentRules.put(new OID(StaticUtils.toLowerCase(d.getOID())), d);
2073      }
2074    }
2075
2076    return ditContentRules;
2077  }
2078
2079
2080
2081  /**
2082   * Compares the DIT structure rule definitions contained in the provided
2083   * schemas.
2084   *
2085   * @param  firstServerSchema   The schema retrieved from the first server.  It
2086   *                             must not be {@code null}.
2087   * @param  secondServerSchema  The schema retrieved from the second server.
2088   *                             It must not be {@code null}.
2089   * @param  numDifferences      A counter used to keep track of the number of
2090   *                             differences found between the schemas.  It must
2091   *                             not be {@code null}.
2092   */
2093  private void compareDITStructureRules(
2094                    @NotNull final Schema firstServerSchema,
2095                    @NotNull final Schema secondServerSchema,
2096                    @NotNull final AtomicInteger numDifferences)
2097  {
2098    // Get the DIT structure rule definitions from each of the schemas.
2099    final Map<Integer,DITStructureRuleDefinition> ditStructureRules1 =
2100         getDITStructureRuleMap(firstServerSchema);
2101    final Map<Integer,DITStructureRuleDefinition> ditStructureRules2 =
2102         getDITStructureRuleMap(secondServerSchema);
2103
2104
2105    // Identify DIT structure rules that exist in one server but not another.
2106    // If any are found, then report them and remove them from the set.
2107    Iterator<Map.Entry<Integer,DITStructureRuleDefinition>> iterator =
2108         ditStructureRules1.entrySet().iterator();
2109    while (iterator.hasNext())
2110    {
2111      final Map.Entry<Integer,DITStructureRuleDefinition> e = iterator.next();
2112      final Integer id = e.getKey();
2113      if (! ditStructureRules2.containsKey(id))
2114      {
2115        reportDifference(
2116             WARN_COMPARE_SCHEMA_MISSING_DIT_STRUCTURE_RULE.get(
2117                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
2118                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
2119             numDifferences,
2120             e.getValue().toString());
2121        iterator.remove();
2122      }
2123    }
2124
2125    iterator = ditStructureRules2.entrySet().iterator();
2126    while (iterator.hasNext())
2127    {
2128      final Map.Entry<Integer,DITStructureRuleDefinition> e = iterator.next();
2129      final Integer oid = e.getKey();
2130      if (! ditStructureRules1.containsKey(oid))
2131      {
2132        reportDifference(
2133             WARN_COMPARE_SCHEMA_MISSING_DIT_STRUCTURE_RULE.get(
2134                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
2135                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
2136             numDifferences,
2137             e.getValue().toString());
2138        iterator.remove();
2139      }
2140    }
2141
2142
2143    // Any remaining DIT structure rules should exist in both servers.  Compare
2144    // them and see if there are any differences between them.
2145    for (final Integer id : ditStructureRules1.keySet())
2146    {
2147      final DITStructureRuleDefinition d1 = ditStructureRules1.get(id);
2148      final DITStructureRuleDefinition d2 = ditStructureRules2.get(id);
2149
2150      final String identifier = compareNames(
2151           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULE.get(),
2152           id.toString(), d1.getNames(), d2.getNames(), numDifferences);
2153
2154      compareStringValues(
2155           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULE.get(),
2156           identifier, INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_NAME_FORM.get(),
2157           d1.getNameFormID(), d2.getNameFormID(), numDifferences);
2158
2159      final String[] superiorRuleIDs1 =
2160           intArrayToStringArray(d1.getSuperiorRuleIDs());
2161      final String[] superiorRuleIDs2 =
2162           intArrayToStringArray(d2.getSuperiorRuleIDs());
2163      compareStringArrayValues(
2164           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULE.get(),
2165           identifier,
2166           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_SUPERIOR_RULE_ID.get(),
2167           superiorRuleIDs1, superiorRuleIDs2, numDifferences);
2168
2169      compareBooleanValues(
2170           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULE.get(),
2171           identifier, INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_OBSOLETE.get(),
2172           d1.isObsolete(), d2.isObsolete(), numDifferences);
2173
2174      if (! ignoreDescriptions)
2175      {
2176      compareStringValues(
2177           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULE.get(),
2178           identifier, INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(),
2179           d1.getDescription(), d2.getDescription(), numDifferences);
2180      }
2181
2182      compareExtensions(
2183           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULE.get(),
2184           identifier, d1.getExtensions(), d2.getExtensions(),
2185           numDifferences);
2186    }
2187  }
2188
2189
2190
2191  /**
2192   * Retrieves a map of the DIT structure rule definitions contained in the
2193   * provided schema, indexed by rule ID.
2194   *
2195   * @param  schema  The schema from which to retrieve the DIT structure rules.
2196   *                 It must not be {@code null}.
2197   *
2198   * @return  A map of the DIT structure rule definitions contained in the
2199   *          provided schema.
2200   */
2201  @NotNull()
2202  private Map<Integer,DITStructureRuleDefinition> getDITStructureRuleMap(
2203               @NotNull final Schema schema)
2204  {
2205    final Map<Integer,DITStructureRuleDefinition> ditStructureRules =
2206         new TreeMap<>();
2207    for (final DITStructureRuleDefinition d : schema.getDITStructureRules())
2208    {
2209      if (includeBasedOnNameAndExtensions(StaticUtils.NO_STRINGS,
2210           d.getExtensions()))
2211      {
2212        ditStructureRules.put(d.getRuleID(), d);
2213      }
2214    }
2215
2216    return ditStructureRules;
2217  }
2218
2219
2220
2221  /**
2222   * Converts the provided integer array to a string array in which each element
2223   * is the string representation of the corresponding element in the provided
2224   * integer array.
2225   *
2226   * @param  intArray  The integer array to convert to a string array.  It must
2227   *                   not be {@code null}, but may be empty.
2228   *
2229   * @return  A string array in which each element is the string representation
2230   *          of the corresponding element in the provided integer array.
2231   */
2232  @NotNull()
2233  private static String[] intArrayToStringArray(@NotNull final int[] intArray)
2234  {
2235    final String[] stringArray = new String[intArray.length];
2236    for (int i=0; i < intArray.length; i++)
2237    {
2238      stringArray[i] = String.valueOf(intArray[i]);
2239    }
2240
2241    return stringArray;
2242  }
2243
2244
2245
2246  /**
2247   * Compares the name form definitions contained in the provided schemas.
2248   *
2249   * @param  firstServerSchema   The schema retrieved from the first server.  It
2250   *                             must not be {@code null}.
2251   * @param  secondServerSchema  The schema retrieved from the second server.
2252   *                             It must not be {@code null}.
2253   * @param  numDifferences      A counter used to keep track of the number of
2254   *                             differences found between the schemas.  It must
2255   *                             not be {@code null}.
2256   */
2257  private void compareNameForms(
2258                    @NotNull final Schema firstServerSchema,
2259                    @NotNull final Schema secondServerSchema,
2260                    @NotNull final AtomicInteger numDifferences)
2261  {
2262    // Get the name form definitions from each of the schemas.
2263    final Map<OID,NameFormDefinition> nameForms1 =
2264         getNameFormMap(firstServerSchema);
2265    final Map<OID,NameFormDefinition> nameForms2 =
2266         getNameFormMap(secondServerSchema);
2267
2268
2269    // Identify name forms that exist in one server but not another.  If
2270    // any are found, then report them and remove them from the set.
2271    Iterator<Map.Entry<OID,NameFormDefinition>> iterator =
2272         nameForms1.entrySet().iterator();
2273    while (iterator.hasNext())
2274    {
2275      final Map.Entry<OID,NameFormDefinition> e = iterator.next();
2276      final OID oid = e.getKey();
2277      if (! nameForms2.containsKey(oid))
2278      {
2279        reportDifference(
2280             WARN_COMPARE_SCHEMA_MISSING_NAME_FORM.get(
2281                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
2282                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
2283             numDifferences,
2284             e.getValue().toString());
2285        iterator.remove();
2286      }
2287    }
2288
2289    iterator = nameForms2.entrySet().iterator();
2290    while (iterator.hasNext())
2291    {
2292      final Map.Entry<OID,NameFormDefinition> e = iterator.next();
2293      final OID oid = e.getKey();
2294      if (! nameForms1.containsKey(oid))
2295      {
2296        reportDifference(
2297             WARN_COMPARE_SCHEMA_MISSING_NAME_FORM.get(
2298                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
2299                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
2300             numDifferences,
2301             e.getValue().toString());
2302        iterator.remove();
2303      }
2304    }
2305
2306
2307    // Any remaining name forms should exist in both servers.  Compare them and
2308    // see if there are any differences between them.
2309    for (final OID oid : nameForms1.keySet())
2310    {
2311      final NameFormDefinition d1 = nameForms1.get(oid);
2312      final NameFormDefinition d2 = nameForms2.get(oid);
2313
2314      final String identifier = compareNames(
2315           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get(),
2316           oid.toString(), d1.getNames(), d2.getNames(), numDifferences);
2317
2318      compareStringValues(
2319           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get(), identifier,
2320           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_STRUCTURAL_CLASS.get(),
2321           d1.getStructuralClass(), d2.getStructuralClass(), numDifferences);
2322
2323      compareStringArrayValues(
2324           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get(), identifier,
2325           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_REQUIRED_ATTRIBUTE.get(),
2326           d1.getRequiredAttributes(), d2.getRequiredAttributes(),
2327           numDifferences);
2328
2329      compareStringArrayValues(
2330           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get(), identifier,
2331           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_OPTIONAL_ATTRIBUTE.get(),
2332           d1.getOptionalAttributes(), d2.getOptionalAttributes(),
2333           numDifferences);
2334
2335      compareBooleanValues(
2336           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get(), identifier,
2337           INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_OBSOLETE.get(),
2338           d1.isObsolete(), d2.isObsolete(), numDifferences);
2339
2340      if (! ignoreDescriptions)
2341      {
2342        compareStringValues(
2343             INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get(), identifier,
2344             INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(),
2345             d1.getDescription(), d2.getDescription(), numDifferences);
2346      }
2347
2348      compareExtensions(
2349           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get(),
2350           identifier, d1.getExtensions(), d2.getExtensions(),
2351           numDifferences);
2352    }
2353  }
2354
2355
2356
2357  /**
2358   * Retrieves a map of the name form definitions contained in the provided
2359   * schema, indexed by OID.
2360   *
2361   * @param  schema  The schema from which to retrieve the name forms.  It must
2362   *                 not be {@code null}.
2363   *
2364   * @return  A map of the name form definitions contained in the provided
2365   *          schema.
2366   */
2367  @NotNull()
2368  private Map<OID,NameFormDefinition> getNameFormMap(
2369               @NotNull final Schema schema)
2370  {
2371    final Map<OID,NameFormDefinition> nameForms = new TreeMap<>();
2372    for (final NameFormDefinition d : schema.getNameForms())
2373    {
2374      if (includeBasedOnNameAndExtensions(d.getNames(), d.getExtensions()))
2375      {
2376        nameForms.put(new OID(StaticUtils.toLowerCase(d.getOID())), d);
2377      }
2378    }
2379
2380    return nameForms;
2381  }
2382
2383
2384
2385  /**
2386   * Compares the matching rule use definitions contained in the provided
2387   * schemas.
2388   *
2389   * @param  firstServerSchema   The schema retrieved from the first server.  It
2390   *                             must not be {@code null}.
2391   * @param  secondServerSchema  The schema retrieved from the second server.
2392   *                             It must not be {@code null}.
2393   * @param  numDifferences      A counter used to keep track of the number of
2394   *                             differences found between the schemas.  It must
2395   *                             not be {@code null}.
2396   */
2397  private void compareMatchingRuleUses(
2398                    @NotNull final Schema firstServerSchema,
2399                    @NotNull final Schema secondServerSchema,
2400                    @NotNull final AtomicInteger numDifferences)
2401  {
2402    // Get the matching rule use definitions from each of the schemas.
2403    final Map<OID,MatchingRuleUseDefinition> matchingRuleUses1 =
2404         getMatchingRuleUseMap(firstServerSchema);
2405    final Map<OID,MatchingRuleUseDefinition> matchingRuleUses2 =
2406         getMatchingRuleUseMap(secondServerSchema);
2407
2408
2409    // Identify matching rule uses that exist in one server but not another.  If
2410    // any are found, then report them and remove them from the set.
2411    Iterator<Map.Entry<OID,MatchingRuleUseDefinition>> iterator =
2412         matchingRuleUses1.entrySet().iterator();
2413    while (iterator.hasNext())
2414    {
2415      final Map.Entry<OID,MatchingRuleUseDefinition> e = iterator.next();
2416      final OID oid = e.getKey();
2417      if (! matchingRuleUses2.containsKey(oid))
2418      {
2419        reportDifference(
2420             WARN_COMPARE_SCHEMA_MISSING_MATCHING_RULE_USE.get(
2421                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
2422                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
2423             numDifferences,
2424             e.getValue().toString());
2425        iterator.remove();
2426      }
2427    }
2428
2429    iterator = matchingRuleUses2.entrySet().iterator();
2430    while (iterator.hasNext())
2431    {
2432      final Map.Entry<OID,MatchingRuleUseDefinition> e = iterator.next();
2433      final OID oid = e.getKey();
2434      if (! matchingRuleUses1.containsKey(oid))
2435      {
2436        reportDifference(
2437             WARN_COMPARE_SCHEMA_MISSING_MATCHING_RULE_USE.get(
2438                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
2439                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
2440             numDifferences,
2441             e.getValue().toString());
2442        iterator.remove();
2443      }
2444    }
2445
2446
2447    // Any remaining matching rule uses should exist in both servers.  Compare
2448    // them and see if there are any differences between them.
2449    for (final OID oid : matchingRuleUses1.keySet())
2450    {
2451      final MatchingRuleUseDefinition d1 = matchingRuleUses1.get(oid);
2452      final MatchingRuleUseDefinition d2 = matchingRuleUses2.get(oid);
2453
2454      final String identifier = compareNames(
2455           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USE.get(),
2456           oid.toString(), d1.getNames(), d2.getNames(), numDifferences);
2457
2458      compareStringArrayValues(
2459           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USE.get(), identifier,
2460           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_APPLICABLE_ATTRIBUTE.get(),
2461           d1.getApplicableAttributeTypes(), d2.getApplicableAttributeTypes(),
2462           numDifferences);
2463
2464      compareBooleanValues(
2465           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USE.get(), identifier,
2466           INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_OBSOLETE.get(),
2467           d1.isObsolete(), d2.isObsolete(), numDifferences);
2468
2469      if (! ignoreDescriptions)
2470      {
2471      compareStringValues(
2472           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USE.get(), identifier,
2473           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(),
2474           d1.getDescription(), d2.getDescription(), numDifferences);
2475      }
2476
2477      compareExtensions(
2478           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USE.get(),
2479           identifier, d1.getExtensions(), d2.getExtensions(),
2480           numDifferences);
2481    }
2482  }
2483
2484
2485
2486  /**
2487   * Retrieves a map of the matching rule use definitions contained in the
2488   * provided schema, indexed by OID.
2489   *
2490   * @param  schema  The schema from which to retrieve the matching rule uses.
2491   *                 It must not be {@code null}.
2492   *
2493   * @return  A map of the matching rule use definitions contained in the
2494   *          provided schema.
2495   */
2496  @NotNull()
2497  private Map<OID,MatchingRuleUseDefinition> getMatchingRuleUseMap(
2498               @NotNull final Schema schema)
2499  {
2500    final Map<OID,MatchingRuleUseDefinition> matchingRuleUses = new TreeMap<>();
2501    for (final MatchingRuleUseDefinition d : schema.getMatchingRuleUses())
2502    {
2503      if (includeBasedOnNameAndExtensions(d.getNames(), d.getExtensions()))
2504      {
2505        matchingRuleUses.put(new OID(StaticUtils.toLowerCase(d.getOID())), d);
2506      }
2507    }
2508
2509    return matchingRuleUses;
2510  }
2511
2512
2513
2514  /**
2515   * Indicates whether to include a schema element with the given name and set
2516   * of extensions.
2517   *
2518   * @param  names       The set of names for the schema element.  It must not
2519   *                     be {@code null}, but may be empty.
2520   * @param  extensions  The set of extensions for the schema element.  It must
2521   *                     not be {@code null}, but may be empty.
2522   *
2523   * @return  {@code true} if an element with the given names and set of
2524   *          extensions should be included, or {@code false} if not.
2525   */
2526  private boolean includeBasedOnNameAndExtensions(
2527               @NotNull final String[] names,
2528               @NotNull final Map<String,String[]> extensions)
2529  {
2530    if (includeOrExcludeBasedOnName && (names.length > 0))
2531    {
2532      boolean includeFound = false;
2533      for (final String name : names)
2534      {
2535        final String lowerName = StaticUtils.toLowerCase(name);
2536        for (final String excludePrefix : excludeNamePrefixes)
2537        {
2538          if (lowerName.startsWith(excludePrefix))
2539          {
2540            return false;
2541          }
2542        }
2543
2544        if (! includeNamePrefixes.isEmpty())
2545        {
2546          for (final String includePrefix : includeNamePrefixes)
2547          {
2548            if (lowerName.startsWith(includePrefix))
2549            {
2550              includeFound = true;
2551              break;
2552            }
2553          }
2554        }
2555      }
2556
2557      if ((! includeNamePrefixes.isEmpty()) && (! includeFound))
2558      {
2559        return false;
2560      }
2561    }
2562
2563
2564    if (includeOrExcludeBasedOnExtensions && (! extensions.isEmpty()))
2565    {
2566      boolean includeFound = false;
2567      for (final Map.Entry<String,String[]> e : extensions.entrySet())
2568      {
2569        final String lowerName = StaticUtils.toLowerCase(e.getKey());
2570        final String[] values = e.getValue();
2571        final String[] lowerValues = new String[values.length];
2572        for (int i=0; i < values.length; i++)
2573        {
2574          lowerValues[i] = StaticUtils.toLowerCase(values[i]);
2575        }
2576
2577        final List<String> excludeValues =
2578             excludeExtensionValues.get(lowerName);
2579        if (excludeValues != null)
2580        {
2581          for (final String lowerValue : lowerValues)
2582          {
2583            if (excludeValues.contains(lowerValue))
2584            {
2585              return false;
2586            }
2587          }
2588        }
2589
2590        final List<String> includeValues =
2591             includeExtensionValues.get(lowerName);
2592        if (includeValues != null)
2593        {
2594          for (final String lowerValue : lowerValues)
2595          {
2596            if (includeValues.contains(lowerValue))
2597            {
2598              includeFound = true;
2599              break;
2600            }
2601          }
2602        }
2603      }
2604
2605      if ((! includeExtensionValues.isEmpty()) && (! includeFound))
2606      {
2607        return false;
2608      }
2609    }
2610
2611
2612    return true;
2613  }
2614
2615
2616
2617  /**
2618   * Reports a difference between schema elements.
2619   *
2620   * @param  message            The message to display with information about
2621   *                            the difference.  It must not be {@code null}.
2622   * @param  numDifferences     A counter used to keep track of the number of
2623   *                            differences found between the schemas.  It must
2624   *                            not be {@code null}.
2625   * @param  additionalStrings  A set of additional strings that should also be
2626   *                            displayed, in addition to the provided message.
2627   *                            Each additional string will be presented on its
2628   *                            own line without any wrapping.  It must not be
2629   *                            {@code null}, but may be empty.
2630   */
2631  private void reportDifference(@NotNull final String message,
2632                                @NotNull final AtomicInteger numDifferences,
2633                                @NotNull final String... additionalStrings)
2634  {
2635    wrapErr(0, WRAP_COLUMN, message);
2636    for (final String additionalString : additionalStrings)
2637    {
2638      err(additionalString);
2639    }
2640    err();
2641    numDifferences.incrementAndGet();
2642  }
2643
2644
2645
2646  /**
2647   * Identifies differences between string values for two schema elements.
2648   *
2649   * @param  elementTypeName    A name for the type of schema element being
2650   *                            compared.  It must not be {@code null}.
2651   * @param  elementIdentifier  An identifier (e.g., the name or OID) for the
2652   *                            schema element for which to make the
2653   *                            determination.  It must not be {@code null}.
2654   * @param  stringDescriptor   A descriptor for the string values being
2655   *                            compared.
2656   * @param  string1            The string value from the first schema element.
2657   *                            It may be {@code null} if the element does not
2658   *                            contain a value for the associated field.
2659   * @param  string2            The string value from the first second element.
2660   *                            It may be {@code null} if the element does not
2661   *                            contain a value for the associated field.
2662   * @param  numDifferences     A counter used to keep track of the number of
2663   *                            differences found between the schemas.  It must
2664   *                            not be {@code null}.
2665   */
2666  private void compareStringValues(@NotNull final String elementTypeName,
2667                                   @NotNull final String elementIdentifier,
2668                                   @NotNull final String stringDescriptor,
2669                                   @Nullable final String string1,
2670                                   @Nullable final String string2,
2671                                   @NotNull final AtomicInteger numDifferences)
2672  {
2673    if (string1 == null)
2674    {
2675      if (string2 != null)
2676      {
2677        reportDifference(
2678             WARN_COMPARE_SCHEMA_STRING_MISSING_FROM_SERVER.get(
2679                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
2680                  elementTypeName, elementIdentifier, stringDescriptor,
2681                  string2, INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
2682             numDifferences);
2683      }
2684    }
2685    else if (string2 == null)
2686    {
2687      reportDifference(
2688           WARN_COMPARE_SCHEMA_STRING_MISSING_FROM_SERVER.get(
2689                INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
2690                elementTypeName, elementIdentifier, stringDescriptor,
2691                string1, INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
2692           numDifferences);
2693    }
2694    else if (! string1.equalsIgnoreCase(string2))
2695    {
2696      reportDifference(
2697           WARN_COMPARE_SCHEMA_STRING_DIFFERENT_BETWEEN_SERVERS.get(
2698                elementTypeName, elementIdentifier, stringDescriptor, string1,
2699                string2),
2700           numDifferences);
2701    }
2702  }
2703
2704
2705
2706  /**
2707   * Identifies differences between the sets of string arrays for two schema
2708   * elements.
2709   *
2710   * @param  elementTypeName    A name for the type of schema element being
2711   *                            compared.  It must not be {@code null}.
2712   * @param  elementIdentifier  An identifier (e.g., the name or OID) for the
2713   *                            schema element for which to make the
2714   *                            determination.  It must not be {@code null}.
2715   * @param  stringDescriptor   A descriptor for the string values being
2716   *                            compared.
2717   * @param  array1             The array of values for the target field from
2718   *                            the element in the first schema.  It must not be
2719   *                            {@code null}, but may be empty.
2720   * @param  array2             The array of values for the target field from
2721   *                            the element in the second schema.  It must not
2722   *                            be {@code null}, but may be empty.
2723   * @param  numDifferences     A counter used to keep track of the number of
2724   *                            differences found between the schemas.  It must
2725   *                            not be {@code null}.
2726   */
2727  private void compareStringArrayValues(
2728                    @NotNull final String elementTypeName,
2729                    @NotNull final String elementIdentifier,
2730                    @NotNull final String stringDescriptor,
2731                    @NotNull final String[] array1,
2732                    @NotNull final String[] array2,
2733                    @NotNull final AtomicInteger numDifferences)
2734  {
2735    if (array1.length == 0)
2736    {
2737      switch (array2.length)
2738      {
2739        case 0:
2740          // The element doesn't have any names in either of the servers, so
2741          // there is no difference.
2742          break;
2743        case 1:
2744          // The element has names in the second server, but not in the first.
2745          reportDifference(
2746               WARN_COMPARE_SCHEMA_STRING_ARRAY_SINGLE_IN_ONLY_ONE_SERVER.get(
2747                    INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
2748                    elementTypeName, elementIdentifier, stringDescriptor,
2749                    array2[0], INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
2750               numDifferences);
2751          break;
2752        default:
2753          reportDifference(
2754               WARN_COMPARE_SCHEMA_STRING_ARRAY_MULTIPLE_IN_ONLY_ONE_SERVER.get(
2755                    INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
2756                    elementTypeName, elementIdentifier, stringDescriptor,
2757                    Arrays.toString(array2),
2758                    INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
2759               numDifferences);
2760          break;
2761      }
2762    }
2763    else if (array2.length == 0)
2764    {
2765      // The element has names in the first server, but not in the second.
2766      if (array1.length == 1)
2767      {
2768        reportDifference(
2769             WARN_COMPARE_SCHEMA_STRING_ARRAY_SINGLE_IN_ONLY_ONE_SERVER.get(
2770                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
2771                  elementTypeName, elementIdentifier, stringDescriptor,
2772                  array1[0], INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
2773             numDifferences);
2774      }
2775      else
2776      {
2777        reportDifference(
2778             WARN_COMPARE_SCHEMA_STRING_ARRAY_MULTIPLE_IN_ONLY_ONE_SERVER.get(
2779                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
2780                  elementTypeName, elementIdentifier, stringDescriptor,
2781                  Arrays.toString(array1),
2782                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
2783             numDifferences);
2784      }
2785    }
2786    else
2787    {
2788      // The element has names in both servers.  See if there are any
2789      // differences between them.
2790      final Map<String,String> n1 = getNameMap(array1);
2791      final Map<String,String> n2 = getNameMap(array2);
2792      for (final Map.Entry<String,String> e : n1.entrySet())
2793      {
2794        final String lowerName = e.getKey();
2795        if (n2.remove(lowerName) == null)
2796        {
2797          reportDifference(
2798               WARN_COMPARE_SCHEMA_STRING_ARRAY_VALUE_MISSING_FROM_SERVER.get(
2799                    elementTypeName, elementIdentifier, stringDescriptor,
2800                    INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
2801                    e.getValue(),
2802                    INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
2803               numDifferences);
2804        }
2805      }
2806
2807      for (final String nameOnlyInServer2 : n2.values())
2808      {
2809        reportDifference(
2810             WARN_COMPARE_SCHEMA_STRING_ARRAY_VALUE_MISSING_FROM_SERVER.get(
2811                  elementTypeName, elementIdentifier, stringDescriptor,
2812                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
2813                  nameOnlyInServer2,
2814                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
2815             numDifferences);
2816      }
2817    }
2818  }
2819
2820
2821
2822  /**
2823   * Identifies differences between the sets of names for two schema elements.
2824   *
2825   * @param  elementTypeName    A name for the type of schema element being
2826   *                            compared.  It must not be {@code null}.
2827   * @param  elementIdentifier  An identifier (e.g., the name or OID) for the
2828   *                            schema element for which to make the
2829   *                            determination.  It must not be {@code null}.
2830   * @param  names1             The set of names for the element from the first
2831   *                            schema.  It must not be {@code null}, but may be
2832   *                            empty.
2833   * @param  names2             The set of names for the element from the second
2834   *                            schema.  It must not be {@code null}, but may be
2835   *                            empty.
2836   * @param  numDifferences     A counter used to keep track of the number of
2837   *                            differences found between the schemas.  It must
2838   *                            not be {@code null}.
2839   *
2840   * @return  The identifier string that should be used to identify the
2841   *          associated schema element.  If both sets of names are non-empty
2842   *          and have the same first element, then that name will be used as
2843   *          the identifier.  Otherwise, the provided identifier will be used.
2844   */
2845  @NotNull()
2846  private String compareNames(@NotNull final String elementTypeName,
2847                              @NotNull final String elementIdentifier,
2848                              @NotNull final String[] names1,
2849                              @NotNull final String[] names2,
2850                              @NotNull final AtomicInteger numDifferences)
2851  {
2852    compareStringArrayValues(elementTypeName, elementIdentifier,
2853         INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_NAME.get(), names1, names2,
2854         numDifferences);
2855
2856
2857    // Identify the best identifier to use for the schema element going forward.
2858    if ((names1.length > 0) && (names2.length > 0) &&
2859         (names1[0].equalsIgnoreCase(names2[0])))
2860    {
2861      return names1[0];
2862    }
2863    else
2864    {
2865      return elementIdentifier;
2866    }
2867  }
2868
2869
2870
2871  /**
2872   * Identifies difference between boolean values for two schema elements.
2873   *
2874   * @param  elementTypeName    A name for the type of schema element being
2875   *                            compared.  It must not be {@code null}.
2876   * @param  elementIdentifier  An identifier (e.g., the name or OID) for the
2877   *                            schema element for which to make the
2878   *                            determination.  It must not be {@code null}.
2879   * @param  booleanFieldName   The name of the Boolean field being compared.
2880   * @param  value1             The Boolean value from the first schema element.
2881   * @param  value2             The Boolean value from the second schema
2882   *                            element.
2883   * @param  numDifferences     A counter used to keep track of the number of
2884   *                            differences found between the schemas.  It must
2885   *                            not be {@code null}.
2886   */
2887  private void compareBooleanValues(@NotNull final String elementTypeName,
2888                                    @NotNull final String elementIdentifier,
2889                                    @NotNull final String booleanFieldName,
2890                                    final boolean value1,
2891                                    final boolean value2,
2892                                    @NotNull final AtomicInteger numDifferences)
2893  {
2894    if (value1 != value2)
2895    {
2896      if (value1)
2897      {
2898        reportDifference(
2899             WARN_COMPARE_SCHEMA_BOOLEAN_DIFFERENCE.get(
2900                  elementTypeName, elementIdentifier, booleanFieldName,
2901                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
2902                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
2903             numDifferences);
2904      }
2905      else
2906      {
2907        reportDifference(
2908             WARN_COMPARE_SCHEMA_BOOLEAN_DIFFERENCE.get(
2909                  elementTypeName, elementIdentifier, booleanFieldName,
2910                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
2911                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
2912             numDifferences);
2913      }
2914    }
2915  }
2916
2917
2918
2919  /**
2920   * Identifies differences between the sets of extensions for two schema
2921   * elements.
2922   *
2923   * @param  elementTypeName    A name for the type of schema element being
2924   *                            compared.  It must not be {@code null}.
2925   * @param  elementIdentifier  An identifier (e.g., the name or OID) for the
2926   *                            schema element for which to make the
2927   *                            determination.  It must not be {@code null}.
2928   * @param  extensions1        The set of extensions for the element from the
2929   *                            first schema.  It must not be {@code null}, but
2930   *                            may be empty.
2931   * @param  extensions2        The set of extensions for the element from the
2932   *                            second schema.  It must not be {@code null}, but
2933   *                            may be empty.
2934   * @param  numDifferences     A counter used to keep track of the number of
2935   *                            differences found between the schemas.  It must
2936   *                            not be {@code null}.
2937   */
2938  private void compareExtensions(
2939                    @NotNull final String elementTypeName,
2940                    @NotNull final String elementIdentifier,
2941                    @NotNull final Map<String,String[]> extensions1,
2942                    @NotNull final Map<String,String[]> extensions2,
2943                    @NotNull final AtomicInteger numDifferences)
2944  {
2945    if (ignoreExtensions)
2946    {
2947      return;
2948    }
2949
2950
2951    // Convert the extensions into a map of sets so that we can alter the
2952    // contents of both the map and its sets.
2953    final Map<String,Set<String>> e1 =
2954         convertToUpdatableExtensionsMap(extensions1);
2955    final Map<String,Set<String>> e2 =
2956         convertToUpdatableExtensionsMap(extensions2);
2957
2958
2959    // Iterate through the extensions and identify differences between them.
2960    for (final Map.Entry<String,Set<String>> e : e1.entrySet())
2961    {
2962      final String extensionName = e.getKey();
2963      final Set<String> extension1Values = e.getValue();
2964      final Set<String> extension2Values = e2.remove(extensionName);
2965      if (extension2Values == null)
2966      {
2967        reportDifference(
2968             WARN_COMPARE_SCHEMA_MISSING_EXTENSION.get(
2969                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
2970                  elementTypeName, elementIdentifier, extensionName,
2971                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
2972             numDifferences);
2973      }
2974      else if (! extension1Values.equals(extension2Values))
2975      {
2976        reportDifference(
2977             WARN_COMPARE_SCHEMA_EXTENSION_DIFFERENCE.get(
2978                  elementTypeName, elementIdentifier, extensionName),
2979             numDifferences);
2980      }
2981    }
2982
2983    for (final String extensionName : e2.keySet())
2984    {
2985      reportDifference(
2986           WARN_COMPARE_SCHEMA_MISSING_EXTENSION.get(
2987                INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
2988                elementTypeName, elementIdentifier, extensionName,
2989                INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
2990           numDifferences);
2991    }
2992  }
2993
2994
2995
2996  /**
2997   * Converts the provided extensions map into an updatable map that associates
2998   * each extension name key with a modifiable set of values rather than an
2999   * array.  In addition, all strings will be converted to lowercase for more
3000   * efficient case-insensitive comparison.
3001   *
3002   * @param  extensionsMap  The map to be converted.  It must not be
3003   *                        {@code null}, but may be empty.
3004   *
3005   * @return  A modifiable map that contains the information in the provided map
3006   *          in a form that is better suited for comparing extensions between
3007   *          two definitions.
3008   */
3009  @NotNull()
3010  private static Map<String,Set<String>> convertToUpdatableExtensionsMap(
3011               @NotNull final Map<String,String[]> extensionsMap)
3012  {
3013    final Map<String,Set<String>> convertedExtensionsMap = new TreeMap<>();
3014    for (final Map.Entry<String,String[]> e : extensionsMap.entrySet())
3015    {
3016      final String lowerExtensionName = StaticUtils.toLowerCase(e.getKey());
3017      final Set<String> lowerExtensionValues = new TreeSet<>();
3018      for (final String extensionValue : e.getValue())
3019      {
3020        lowerExtensionValues.add(StaticUtils.toLowerCase(extensionValue));
3021      }
3022
3023      convertedExtensionsMap.put(lowerExtensionName, lowerExtensionValues);
3024    }
3025
3026    return convertedExtensionsMap;
3027  }
3028
3029
3030
3031  /**
3032   * Retrieves a modifiable map containing the provided names.  The key for each
3033   * entry in the map will be the name in all lowercase, and the value will be
3034   * the name in the case in which it is provided.
3035   *
3036   * @param  names  The names to include in the resulting map.  It must not be
3037   *                {@code null}.
3038   *
3039   * @return  A modifiable map containing the provided names.
3040   */
3041  @NotNull()
3042  private static Map<String,String> getNameMap(@NotNull final String[] names)
3043  {
3044    final Map<String,String> m = new TreeMap<>();
3045    for (final String name : names)
3046    {
3047      m.put(StaticUtils.toLowerCase(name), name);
3048    }
3049
3050    return m;
3051  }
3052
3053
3054
3055  /**
3056   * Logs the provided message to standard error and sets it as the tool
3057   * completion message.
3058   *
3059   * @param  message  The completion message.  It must not be {@code null}.
3060   */
3061  private void logCompletionError(@NotNull final String message)
3062  {
3063    completionMessageRef.compareAndSet(null, message);
3064    wrapErr(0, WRAP_COLUMN, message);
3065  }
3066
3067
3068
3069  /**
3070   * {@inheritDoc}
3071   */
3072  @Override()
3073  @NotNull()
3074  public LinkedHashMap<String[],String> getExampleUsages()
3075  {
3076    final LinkedHashMap<String[],String> examples = new LinkedHashMap<>();
3077
3078    examples.put(
3079         new String[]
3080         {
3081           "--firstHostname", "ds1.example.com",
3082           "--firstPort", "636",
3083           "--firstUseSSL",
3084           "--firstBindDN", "cn=Directory Manager",
3085           "--firstBindPasswordFile", "/path/to/password.txt",
3086           "--secondHostname", "ds2.example.com",
3087           "--secondPort", "636",
3088           "--secondUseSSL",
3089           "--secondBindDN", "cn=Directory Manager",
3090           "--secondBindPasswordFile", "/path/to/password.txt"
3091         },
3092         INFO_COMPARE_LDAP_SCHEMAS_EXAMPLE.get());
3093
3094    return examples;
3095  }
3096}