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 supportsDebugLogging()
843  {
844    return true;
845  }
846
847
848
849  /**
850   * {@inheritDoc}
851   */
852  @Override()
853  protected boolean logToolInvocationByDefault()
854  {
855    return false;
856  }
857
858
859
860  /**
861   * {@inheritDoc}
862   */
863  @Override()
864  @Nullable()
865  protected String getToolCompletionMessage()
866  {
867    return completionMessageRef.get();
868  }
869
870
871
872  /**
873   * {@inheritDoc}
874   */
875  @Override()
876  @NotNull()
877  public ResultCode doToolProcessing()
878  {
879    // Get the schemas from each of the servers.
880    final Schema firstServerSchema;
881    final Map<String,LDAPException> firstUnparsableAttributeSyntaxes =
882         new LinkedHashMap<>();
883    final Map<String,LDAPException> firstUnparsableMatchingRules =
884         new LinkedHashMap<>();
885    final Map<String,LDAPException> firstUnparsableAttributeTypes =
886         new LinkedHashMap<>();
887    final Map<String,LDAPException> firstUnparsableObjectClasses =
888         new LinkedHashMap<>();
889    final Map<String,LDAPException> firstUnparsableDITContentRules =
890         new LinkedHashMap<>();
891    final Map<String,LDAPException> firstUnparsableDITStructureRules =
892         new LinkedHashMap<>();
893    final Map<String,LDAPException> firstUnparsableNameForms =
894         new LinkedHashMap<>();
895    final Map<String,LDAPException> firstUnparsableMatchingRuleUses =
896         new LinkedHashMap<>();
897    try
898    {
899      firstServerSchema = getSchema(FIRST_SERVER_INDEX,
900           ARG_NAME_FIRST_SCHEMA_ENTRY_DN,
901           INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
902           firstUnparsableAttributeSyntaxes, firstUnparsableMatchingRules,
903           firstUnparsableAttributeTypes, firstUnparsableObjectClasses,
904           firstUnparsableDITContentRules, firstUnparsableDITStructureRules,
905           firstUnparsableNameForms, firstUnparsableMatchingRuleUses);
906    }
907    catch (final LDAPException e)
908    {
909      logCompletionError(e.getMessage());
910      return e.getResultCode();
911    }
912
913    final Schema secondServerSchema;
914    final Map<String,LDAPException> secondUnparsableAttributeSyntaxes =
915         new LinkedHashMap<>();
916    final Map<String,LDAPException> secondUnparsableMatchingRules =
917         new LinkedHashMap<>();
918    final Map<String,LDAPException> secondUnparsableAttributeTypes =
919         new LinkedHashMap<>();
920    final Map<String,LDAPException> secondUnparsableObjectClasses =
921         new LinkedHashMap<>();
922    final Map<String,LDAPException> secondUnparsableDITContentRules =
923         new LinkedHashMap<>();
924    final Map<String,LDAPException> secondUnparsableDITStructureRules =
925         new LinkedHashMap<>();
926    final Map<String,LDAPException> secondUnparsableNameForms =
927         new LinkedHashMap<>();
928    final Map<String,LDAPException> secondUnparsableMatchingRuleUses =
929         new LinkedHashMap<>();
930    try
931    {
932      secondServerSchema = getSchema(SECOND_SERVER_INDEX,
933           ARG_NAME_SECOND_SCHEMA_ENTRY_DN,
934           INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
935           secondUnparsableAttributeSyntaxes, secondUnparsableMatchingRules,
936           secondUnparsableAttributeTypes, secondUnparsableObjectClasses,
937           secondUnparsableDITContentRules, secondUnparsableDITStructureRules,
938           secondUnparsableNameForms, secondUnparsableMatchingRuleUses);
939    }
940    catch (final LDAPException e)
941    {
942      logCompletionError(e.getMessage());
943      return e.getResultCode();
944    }
945
946
947    // Report on any unparsable schema elements.
948    final AtomicReference<ResultCode> resultCodeRef = new AtomicReference<>();
949    boolean unparsableElementsEncountered = reportUnparsableSchemaElements(
950         INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
951         firstUnparsableAttributeSyntaxes, firstUnparsableMatchingRules,
952         firstUnparsableAttributeTypes, firstUnparsableObjectClasses,
953         firstUnparsableDITContentRules, firstUnparsableDITStructureRules,
954         firstUnparsableNameForms, firstUnparsableMatchingRuleUses);
955
956    unparsableElementsEncountered |= reportUnparsableSchemaElements(
957         INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
958         secondUnparsableAttributeSyntaxes, secondUnparsableMatchingRules,
959         secondUnparsableAttributeTypes, secondUnparsableObjectClasses,
960         secondUnparsableDITContentRules, secondUnparsableDITStructureRules,
961         secondUnparsableNameForms, secondUnparsableMatchingRuleUses);
962
963    if (unparsableElementsEncountered)
964    {
965      resultCodeRef.set(ResultCode.INVALID_ATTRIBUTE_SYNTAX);
966    }
967
968
969    // Validate the different types of schema elements.
970    final AtomicInteger numDifferences = new AtomicInteger();
971    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAXES))
972    {
973      compareAttributeSyntaxes(firstServerSchema, secondServerSchema,
974           numDifferences);
975    }
976
977    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_MATCHING_RULES))
978    {
979      compareMatchingRules(firstServerSchema, secondServerSchema,
980           numDifferences);
981    }
982
983    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPES))
984    {
985      compareAttributeTypes(firstServerSchema, secondServerSchema,
986           numDifferences);
987    }
988
989    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_OBJECT_CLASSES))
990    {
991      compareObjectClasses(firstServerSchema, secondServerSchema,
992           numDifferences);
993    }
994
995    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULES))
996    {
997      compareDITContentRules(firstServerSchema, secondServerSchema,
998           numDifferences);
999    }
1000
1001    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULES))
1002    {
1003      compareDITStructureRules(firstServerSchema, secondServerSchema,
1004           numDifferences);
1005    }
1006
1007    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_NAME_FORMS))
1008    {
1009      compareNameForms(firstServerSchema, secondServerSchema, numDifferences);
1010    }
1011
1012    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USES))
1013    {
1014      compareMatchingRuleUses(firstServerSchema, secondServerSchema,
1015           numDifferences);
1016    }
1017
1018
1019    // If any errors were encountered, then return an error result code.
1020    // Otherwise, if any differences were encountered, then return a
1021    // COMPARE_FALSE result code.  Otherwise, return a SUCCESS result code.
1022    final int differenceCount = numDifferences.get();
1023    if (unparsableElementsEncountered)
1024    {
1025      switch (differenceCount)
1026      {
1027        case 0:
1028          logCompletionError(
1029               ERR_COMPARE_SCHEMA_SUMMARY_UNPARSABLE_NO_DIFFERENCES.get());
1030          break;
1031        case 1:
1032          logCompletionError(
1033               ERR_COMPARE_SCHEMA_SUMMARY_UNPARSABLE_WITH_DIFFERENCE.get());
1034          break;
1035        default:
1036          logCompletionError(
1037               ERR_COMPARE_SCHEMA_SUMMARY_UNPARSABLE_WITH_DIFFERENCES.get(
1038                    differenceCount));
1039          break;
1040      }
1041    }
1042    else if (differenceCount > 0)
1043    {
1044      resultCodeRef.compareAndSet(null, ResultCode.COMPARE_FALSE);
1045      if (differenceCount == 1)
1046      {
1047        logCompletionError(
1048             ERR_COMPARE_SCHEMA_SUMMARY_DIFFERENCE.get());
1049      }
1050      else
1051      {
1052        logCompletionError(
1053             ERR_COMPARE_SCHEMA_SUMMARY_DIFFERENCES.get(differenceCount));
1054      }
1055    }
1056    else
1057    {
1058      resultCodeRef.compareAndSet(null, ResultCode.SUCCESS);
1059      final String message = INFO_COMPARE_SCHEMA_SUMMARY_NO_DIFFERENCES.get();
1060      completionMessageRef.compareAndSet(null, message);
1061      wrapOut(0, WRAP_COLUMN, message);
1062    }
1063
1064    return resultCodeRef.get();
1065  }
1066
1067
1068
1069  /**
1070   * Retrieves the schema from the specified server.
1071   *
1072   * @param  serverIndex
1073   *              The index for the server from which to retrieve the schema.
1074   * @param  schemaDNArgName
1075   *              The name of the argument to use to retrieve the DN of the
1076   *              subschema subentry, if specified.  It must not be
1077   *              {@code null}.
1078   * @param  serverLabel
1079   *              The label to use to refer to the server.  It must not be
1080   *              {@code null}.
1081   * @param  unparsableAttributeSyntaxes
1082   *              A map that will be updated with information about any
1083   *              unparsable attribute syntax definitions found in the schema
1084   *              from the specified server.  Each key will be the unparsable
1085   *              definition, and the corresponding value will be the exception
1086   *              caught while trying to parse it.  It must not be {@code null}.
1087   * @param  unparsableMatchingRules
1088   *              A map that will be updated with information about any
1089   *              unparsable matching rule definitions found in the schema
1090   *              from the specified server.  Each key will be the unparsable
1091   *              definition, and the corresponding value will be the exception
1092   *              caught while trying to parse it.  It must not be {@code null}.
1093   * @param  unparsableAttributeTypes
1094   *              A map that will be updated with information about any
1095   *              unparsable attribute type definitions found in the schema
1096   *              from the specified server.  Each key will be the unparsable
1097   *              definition, and the corresponding value will be the exception
1098   *              caught while trying to parse it.  It must not be {@code null}.
1099   * @param  unparsableObjectClasses
1100   *              A map that will be updated with information about any
1101   *              unparsable object class definitions found in the schema
1102   *              from the specified server.  Each key will be the unparsable
1103   *              definition, and the corresponding value will be the exception
1104   *              caught while trying to parse it.  It must not be {@code null}.
1105   * @param  unparsableDITContentRules
1106   *              A map that will be updated with information about any
1107   *              unparsable DIT content rule definitions found in the schema
1108   *              from the specified server.  Each key will be the unparsable
1109   *              definition, and the corresponding value will be the exception
1110   *              caught while trying to parse it.  It must not be {@code null}.
1111   * @param  unparsableDITStructureRules
1112   *              A map that will be updated with information about any
1113   *              unparsable DIT structure rule definitions found in the schema
1114   *              from the specified server.  Each key will be the unparsable
1115   *              definition, and the corresponding value will be the exception
1116   *              caught while trying to parse it.  It must not be {@code null}.
1117   * @param  unparsableNameForms
1118   *              A map that will be updated with information about any
1119   *              unparsable name form definitions found in the schema
1120   *              from the specified server.  Each key will be the unparsable
1121   *              definition, and the corresponding value will be the exception
1122   *              caught while trying to parse it.  It must not be {@code null}.
1123   * @param  unparsableMatchingRuleUses
1124   *              A map that will be updated with information about any
1125   *              unparsable matching rule use definitions found in the schema
1126   *              from the specified server.  Each key will be the unparsable
1127   *              definition, and the corresponding value will be the exception
1128   *              caught while trying to parse it.  It must not be {@code null}.
1129   *
1130   * @return  The schema retrieved from the server.
1131   *
1132   * @throws  LDAPException  If a problem occurs while attempting to obtain the
1133   *                         schema.
1134   */
1135  @NotNull()
1136  private Schema getSchema(final int serverIndex,
1137       @NotNull final String schemaDNArgName,
1138       @NotNull final String serverLabel,
1139       @NotNull final Map<String,LDAPException> unparsableAttributeSyntaxes,
1140       @NotNull final Map<String,LDAPException> unparsableMatchingRules,
1141       @NotNull final Map<String,LDAPException> unparsableAttributeTypes,
1142       @NotNull final Map<String,LDAPException> unparsableObjectClasses,
1143       @NotNull final Map<String,LDAPException> unparsableDITContentRules,
1144       @NotNull final Map<String,LDAPException> unparsableDITStructureRules,
1145       @NotNull final Map<String,LDAPException> unparsableNameForms,
1146       @NotNull final Map<String,LDAPException> unparsableMatchingRuleUses)
1147       throws LDAPException
1148  {
1149    // Establish a connection to the server.
1150    final LDAPConnection conn;
1151    try
1152    {
1153      conn = getConnection(serverIndex);
1154    }
1155    catch (final LDAPException e)
1156    {
1157      Debug.debugException(e);
1158      throw new LDAPException(e.getResultCode(),
1159           ERR_COMPARE_SCHEMA_CANNOT_CONNECT.get(serverLabel, e.getMessage()),
1160           e);
1161    }
1162
1163    final ArgumentParser parser = parserRef.get();
1164    final BooleanArgument getExtendedSchemaInfoArg =
1165         parser.getBooleanArgument(ARG_NAME_GET_EXTENDED_SCHEMA_INFO);
1166    final boolean getExtendedSchemaInfo =
1167         ((getExtendedSchemaInfoArg != null) &&
1168              getExtendedSchemaInfoArg.isPresent());
1169
1170
1171    try
1172    {
1173      // See if the schema entry DN was specified as an argument.  If so, then
1174      // retrieve that entry and parse it as a schema entry.  Otherwise, use the
1175      // default method for obtaining the schema.
1176      final String schemaEntryDN;
1177      final DNArgument schemaEntryDNArg = parser.getDNArgument(schemaDNArgName);
1178      if (schemaEntryDNArg.isPresent())
1179      {
1180        schemaEntryDN = schemaEntryDNArg.getStringValue();
1181      }
1182      else
1183      {
1184        final RootDSE rootDSE = conn.getRootDSE();
1185        if (rootDSE == null)
1186        {
1187          throw new LDAPException(ResultCode.LOCAL_ERROR,
1188               ERR_COMPARE_SCHEMA_CANNOT_GET_ROOT_DSE.get(serverLabel));
1189        }
1190
1191        schemaEntryDN = rootDSE.getSubschemaSubentryDN();
1192        if (schemaEntryDN == null)
1193        {
1194          throw new LDAPException(ResultCode.LOCAL_ERROR,
1195               ERR_COMPARE_SCHEMA_CANNOT_GET_ROOT_DSE_SCHEMA_DN.get(serverLabel,
1196                    RootDSE.ATTR_SUBSCHEMA_SUBENTRY));
1197        }
1198      }
1199
1200      final SearchRequest searchRequest = new SearchRequest(schemaEntryDN,
1201           SearchScope.BASE, Schema.SUBSCHEMA_SUBENTRY_FILTER,
1202           Schema.SCHEMA_REQUEST_ATTRS);
1203      if (getExtendedSchemaInfo)
1204      {
1205        searchRequest.addControl(new ExtendedSchemaInfoRequestControl(false));
1206      }
1207
1208      final Entry schemaEntry = conn.searchForEntry(searchRequest);
1209      if (schemaEntry == null)
1210      {
1211        throw new LDAPException(ResultCode.NO_SUCH_OBJECT,
1212             ERR_COMPARE_SCHEMA_CANNOT_GET_SCHEMA_ENTRY.get(
1213                  String.valueOf(schemaEntryDN), serverLabel));
1214      }
1215
1216      return new Schema(schemaEntry, unparsableAttributeSyntaxes,
1217           unparsableMatchingRules, unparsableAttributeTypes,
1218           unparsableObjectClasses, unparsableDITContentRules,
1219           unparsableDITStructureRules, unparsableNameForms,
1220           unparsableMatchingRuleUses);
1221    }
1222    catch (final LDAPException e)
1223    {
1224      Debug.debugException(e);
1225      throw new LDAPException(e.getResultCode(),
1226           ERR_COMPARE_SCHEMA_CANNOT_GET_SCHEMA.get(serverLabel,
1227                e.getMessage()),
1228           e);
1229    }
1230    finally
1231    {
1232      conn.close();
1233    }
1234  }
1235
1236
1237
1238  /**
1239   * Reports error messages about any unparsable elements found in a server's
1240   * schema.
1241   *
1242   * @param  serverLabel
1243   *              The label for the associated directory server instance.
1244   * @param  unparsableAttributeSyntaxes
1245   *              A map with information about any unparsable attribute syntax
1246   *              definitions found in the schema.
1247   * @param  unparsableMatchingRules
1248   *              A map with information about any unparsable matching rule
1249   *              definitions found in the schema.
1250   * @param  unparsableAttributeTypes
1251   *              A map with information about any unparsable attribute type
1252   *              definitions found in the schema.
1253   * @param  unparsableObjectClasses
1254   *              A map with information about any unparsable object class
1255   *              definitions found in the schema.
1256   * @param  unparsableDITContentRules
1257   *              A map with information about any unparsable DIT content rule
1258   *              definitions found in the schema.
1259   * @param  unparsableDITStructureRules
1260   *              A map with information about any unparsable DIT structure rule
1261   *              definitions found in the schema.
1262   * @param  unparsableNameForms
1263   *              A map with information about any unparsable name form
1264   *              definitions found in the schema.
1265   * @param  unparsableMatchingRuleUses
1266   *              A map with information about any unparsable matching rule use
1267   *              definitions found in the schema.
1268   *
1269   * @return  {@code true} if the schema contained any unparsable elements, or
1270   *          {@code false} if not.
1271   */
1272  private boolean reportUnparsableSchemaElements(
1273       @NotNull final String serverLabel,
1274       @NotNull final Map<String,LDAPException> unparsableAttributeSyntaxes,
1275       @NotNull final Map<String,LDAPException> unparsableMatchingRules,
1276       @NotNull final Map<String,LDAPException> unparsableAttributeTypes,
1277       @NotNull final Map<String,LDAPException> unparsableObjectClasses,
1278       @NotNull final Map<String,LDAPException> unparsableDITContentRules,
1279       @NotNull final Map<String,LDAPException> unparsableDITStructureRules,
1280       @NotNull final Map<String,LDAPException> unparsableNameForms,
1281       @NotNull final Map<String,LDAPException> unparsableMatchingRuleUses)
1282  {
1283    boolean unparsableFound = false;
1284
1285    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAXES))
1286    {
1287      unparsableFound |= reportUnparsableSchemaElements(serverLabel,
1288           unparsableAttributeSyntaxes,
1289           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAX.get());
1290    }
1291
1292    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_MATCHING_RULES))
1293    {
1294      unparsableFound |= reportUnparsableSchemaElements(serverLabel,
1295           unparsableMatchingRules,
1296           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE.get());
1297    }
1298
1299    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPES))
1300    {
1301      unparsableFound |= reportUnparsableSchemaElements(serverLabel,
1302           unparsableAttributeTypes,
1303           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get());
1304    }
1305
1306    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_OBJECT_CLASSES))
1307    {
1308      unparsableFound |= reportUnparsableSchemaElements(serverLabel,
1309           unparsableObjectClasses,
1310           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get());
1311    }
1312
1313    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULES))
1314    {
1315      unparsableFound |= reportUnparsableSchemaElements(serverLabel,
1316           unparsableDITContentRules,
1317           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get());
1318    }
1319
1320    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULES))
1321    {
1322      unparsableFound |= reportUnparsableSchemaElements(serverLabel,
1323           unparsableDITStructureRules,
1324           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULE.get());
1325    }
1326
1327    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_NAME_FORMS))
1328    {
1329      unparsableFound |= reportUnparsableSchemaElements(serverLabel,
1330           unparsableNameForms,
1331           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get());
1332    }
1333
1334    if (schemaElementTypes.contains(SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USES))
1335    {
1336      unparsableFound |= reportUnparsableSchemaElements(serverLabel,
1337           unparsableMatchingRuleUses,
1338           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USE.get());
1339    }
1340
1341    return unparsableFound;
1342  }
1343
1344
1345
1346  /**
1347   * Reports error messages about any unparsable elements of the specified type
1348   * found in a server's schema.
1349   *
1350   * @param  serverLabel         The label for the associated directory server
1351   *                             instance.  It must not be {@code null}.
1352   * @param  unparsableElements  The set of unparsable elements of a given type.
1353   *                             It must not be {@code null}, but may be empty.
1354   * @param  elementTypeName     The name of the schema element type.  It must
1355   *                             not be {@code null}.
1356   *
1357   * @return  {@code true} if the provided map contained information about one
1358   *          or more unparsable elements, or {@code false} if not.
1359   */
1360  private boolean reportUnparsableSchemaElements(
1361       @NotNull final String serverLabel,
1362       @NotNull final Map<String,LDAPException> unparsableElements,
1363       @NotNull final String elementTypeName)
1364  {
1365    for (final Map.Entry<String,LDAPException> e :
1366         unparsableElements.entrySet())
1367    {
1368      wrapErr(0, WRAP_COLUMN,
1369           ERR_COMPARE_SCHEMA_UNPARSABLE_ELEMENT.get(elementTypeName,
1370                serverLabel, e.getValue().getMessage()));
1371      err(e.getKey());
1372      err();
1373    }
1374
1375    return (! unparsableElements.isEmpty());
1376  }
1377
1378
1379
1380  /**
1381   * Compares the attribute syntax definitions contained in the provided
1382   * schemas.
1383   *
1384   * @param  firstServerSchema   The schema retrieved from the first server.  It
1385   *                             must not be {@code null}.
1386   * @param  secondServerSchema  The schema retrieved from the second server.
1387   *                             It must not be {@code null}.
1388   * @param  numDifferences      A counter used to keep track of the number of
1389   *                             differences found between the schemas.  It must
1390   *                             not be {@code null}.
1391   */
1392  private void compareAttributeSyntaxes(
1393                    @NotNull final Schema firstServerSchema,
1394                    @NotNull final Schema secondServerSchema,
1395                    @NotNull final AtomicInteger numDifferences)
1396  {
1397    // Get the attribute syntax definitions from each of the schemas.
1398    final Map<OID,AttributeSyntaxDefinition> syntaxes1 =
1399         getAttributeSyntaxMap(firstServerSchema);
1400    final Map<OID,AttributeSyntaxDefinition> syntaxes2 =
1401         getAttributeSyntaxMap(secondServerSchema);
1402
1403
1404    // Identify syntaxes that exist in one server but not another.  If any are
1405    // found, then report them and remove them from the set.
1406    Iterator<Map.Entry<OID,AttributeSyntaxDefinition>> iterator =
1407         syntaxes1.entrySet().iterator();
1408    while (iterator.hasNext())
1409    {
1410      final Map.Entry<OID,AttributeSyntaxDefinition> e = iterator.next();
1411      final OID oid = e.getKey();
1412      if (! syntaxes2.containsKey(oid))
1413      {
1414        reportDifference(
1415             WARN_COMPARE_SCHEMA_MISSING_SYNTAX.get(
1416                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
1417                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
1418             numDifferences,
1419             e.getValue().toString());
1420        iterator.remove();
1421      }
1422    }
1423
1424    iterator = syntaxes2.entrySet().iterator();
1425    while (iterator.hasNext())
1426    {
1427      final Map.Entry<OID,AttributeSyntaxDefinition> e = iterator.next();
1428      final OID oid = e.getKey();
1429      if (! syntaxes1.containsKey(oid))
1430      {
1431        reportDifference(
1432             WARN_COMPARE_SCHEMA_MISSING_SYNTAX.get(
1433                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
1434                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
1435             numDifferences,
1436             e.getValue().toString());
1437        iterator.remove();
1438      }
1439    }
1440
1441
1442    // Any remaining syntaxes should exist in both servers.  Compare them and
1443    // see if there are any differences between them.
1444    for (final OID oid : syntaxes1.keySet())
1445    {
1446      final AttributeSyntaxDefinition d1 = syntaxes1.get(oid);
1447      final AttributeSyntaxDefinition d2 = syntaxes2.get(oid);
1448
1449      if (! ignoreDescriptions)
1450      {
1451        compareStringValues(
1452             INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAX.get(),
1453             oid.toString(),
1454             INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(),
1455             d1.getDescription(), d2.getDescription(), numDifferences);
1456      }
1457
1458      compareExtensions(
1459           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAX.get(),
1460           oid.toString(), d1.getExtensions(), d2.getExtensions(),
1461           numDifferences);
1462    }
1463  }
1464
1465
1466
1467  /**
1468   * Retrieves a map of the attribute syntax definitions contained in the
1469   * provided schema, indexed by OID.
1470   *
1471   * @param  schema  The schema from which to retrieve the attribute syntaxes.
1472   *                 It must not be {@code null}.
1473   *
1474   * @return  A map of the attribute syntax definitions contained in the
1475   *          provided schema.
1476   */
1477  @NotNull()
1478  private Map<OID,AttributeSyntaxDefinition> getAttributeSyntaxMap(
1479               @NotNull final Schema schema)
1480  {
1481    final Map<OID,AttributeSyntaxDefinition> syntaxes = new TreeMap<>();
1482    for (final AttributeSyntaxDefinition d : schema.getAttributeSyntaxes())
1483    {
1484      if (includeBasedOnNameAndExtensions(StaticUtils.NO_STRINGS,
1485           d.getExtensions()))
1486      {
1487        syntaxes.put(new OID(StaticUtils.toLowerCase(d.getOID())), d);
1488      }
1489    }
1490
1491    return syntaxes;
1492  }
1493
1494
1495
1496  /**
1497   * Compares the matching rule definitions contained in the provided schemas.
1498   *
1499   * @param  firstServerSchema   The schema retrieved from the first server.  It
1500   *                             must not be {@code null}.
1501   * @param  secondServerSchema  The schema retrieved from the second server.
1502   *                             It must not be {@code null}.
1503   * @param  numDifferences      A counter used to keep track of the number of
1504   *                             differences found between the schemas.  It must
1505   *                             not be {@code null}.
1506   */
1507  private void compareMatchingRules(
1508                    @NotNull final Schema firstServerSchema,
1509                    @NotNull final Schema secondServerSchema,
1510                    @NotNull final AtomicInteger numDifferences)
1511  {
1512    // Get the matching rule definitions from each of the schemas.
1513    final Map<OID,MatchingRuleDefinition> matchingRules1 =
1514         getMatchingRuleMap(firstServerSchema);
1515    final Map<OID,MatchingRuleDefinition> matchingRules2 =
1516         getMatchingRuleMap(secondServerSchema);
1517
1518
1519    // Identify matching rules that exist in one server but not another.  If any
1520    // are found, then report them and remove them from the set.
1521    Iterator<Map.Entry<OID,MatchingRuleDefinition>> iterator =
1522         matchingRules1.entrySet().iterator();
1523    while (iterator.hasNext())
1524    {
1525      final Map.Entry<OID,MatchingRuleDefinition> e = iterator.next();
1526      final OID oid = e.getKey();
1527      if (! matchingRules2.containsKey(oid))
1528      {
1529        reportDifference(
1530             WARN_COMPARE_SCHEMA_MISSING_MATCHING_RULE.get(
1531                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
1532                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
1533             numDifferences,
1534        e.getValue().toString());
1535        iterator.remove();
1536      }
1537    }
1538
1539    iterator = matchingRules2.entrySet().iterator();
1540    while (iterator.hasNext())
1541    {
1542      final Map.Entry<OID,MatchingRuleDefinition> e = iterator.next();
1543      final OID oid = e.getKey();
1544      if (! matchingRules1.containsKey(oid))
1545      {
1546        reportDifference(
1547             WARN_COMPARE_SCHEMA_MISSING_MATCHING_RULE.get(
1548                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
1549                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
1550             numDifferences,
1551             e.getValue().toString());
1552        iterator.remove();
1553      }
1554    }
1555
1556
1557    // Any remaining matching rules should exist in both servers.  Compare them
1558    // and see if there are any differences between them.
1559    for (final OID oid : matchingRules1.keySet())
1560    {
1561      final MatchingRuleDefinition d1 = matchingRules1.get(oid);
1562      final MatchingRuleDefinition d2 = matchingRules2.get(oid);
1563
1564      final String identifier = compareNames(
1565           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE.get(),
1566           oid.toString(), d1.getNames(), d2.getNames(), numDifferences);
1567
1568      compareStringValues(
1569           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE.get(), identifier,
1570           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_SYNTAX_OID.get(),
1571           d1.getSyntaxOID(), d2.getSyntaxOID(), numDifferences);
1572
1573      compareBooleanValues(
1574           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE.get(), identifier,
1575           INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_OBSOLETE.get(),
1576           d1.isObsolete(), d2.isObsolete(), numDifferences);
1577
1578      if (! ignoreDescriptions)
1579      {
1580        compareStringValues(
1581             INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE.get(), identifier,
1582             INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(),
1583             d1.getDescription(), d2.getDescription(), numDifferences);
1584      }
1585
1586      compareExtensions(
1587           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAX.get(),
1588           identifier, d1.getExtensions(), d2.getExtensions(),
1589           numDifferences);
1590    }
1591  }
1592
1593
1594
1595  /**
1596   * Retrieves a map of the matching rule definitions contained in the provided
1597   * schema, indexed by OID.
1598   *
1599   * @param  schema  The schema from which to retrieve the matching rules.  It
1600   *                 must not be {@code null}.
1601   *
1602   * @return  A map of the matching rule definitions contained in the provided
1603   *          schema.
1604   */
1605  @NotNull()
1606  private Map<OID,MatchingRuleDefinition> getMatchingRuleMap(
1607               @NotNull final Schema schema)
1608  {
1609    final Map<OID,MatchingRuleDefinition> matchingRules = new TreeMap<>();
1610    for (final MatchingRuleDefinition d : schema.getMatchingRules())
1611    {
1612      if (includeBasedOnNameAndExtensions(d.getNames(), d.getExtensions()))
1613      {
1614        matchingRules.put(new OID(StaticUtils.toLowerCase(d.getOID())), d);
1615      }
1616    }
1617
1618    return matchingRules;
1619  }
1620
1621
1622
1623  /**
1624   * Compares the attribute type definitions contained in the provided schemas.
1625   *
1626   * @param  firstServerSchema   The schema retrieved from the first server.  It
1627   *                             must not be {@code null}.
1628   * @param  secondServerSchema  The schema retrieved from the second server.
1629   *                             It must not be {@code null}.
1630   * @param  numDifferences      A counter used to keep track of the number of
1631   *                             differences found between the schemas.  It must
1632   *                             not be {@code null}.
1633   */
1634  private void compareAttributeTypes(
1635                    @NotNull final Schema firstServerSchema,
1636                    @NotNull final Schema secondServerSchema,
1637                    @NotNull final AtomicInteger numDifferences)
1638  {
1639    // Get the attribute type definitions from each of the schemas.
1640    final Map<OID,AttributeTypeDefinition> attributeTypes1 =
1641         getAttributeTypeMap(firstServerSchema);
1642    final Map<OID,AttributeTypeDefinition> attributeTypes2 =
1643         getAttributeTypeMap(secondServerSchema);
1644
1645
1646    // Identify attribute types that exist in one server but not another.  If
1647    // any are found, then report them and remove them from the set.
1648    Iterator<Map.Entry<OID,AttributeTypeDefinition>> iterator =
1649         attributeTypes1.entrySet().iterator();
1650    while (iterator.hasNext())
1651    {
1652      final Map.Entry<OID,AttributeTypeDefinition> e = iterator.next();
1653      final OID oid = e.getKey();
1654      if (! attributeTypes2.containsKey(oid))
1655      {
1656        reportDifference(
1657             WARN_COMPARE_SCHEMA_MISSING_ATTRIBUTE_TYPE.get(
1658                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
1659                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
1660             numDifferences,
1661             e.getValue().toString());
1662        iterator.remove();
1663      }
1664    }
1665
1666    iterator = attributeTypes2.entrySet().iterator();
1667    while (iterator.hasNext())
1668    {
1669      final Map.Entry<OID,AttributeTypeDefinition> e = iterator.next();
1670      final OID oid = e.getKey();
1671      if (! attributeTypes1.containsKey(oid))
1672      {
1673        reportDifference(
1674             WARN_COMPARE_SCHEMA_MISSING_ATTRIBUTE_TYPE.get(
1675                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
1676                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
1677             numDifferences,
1678             e.getValue().toString());
1679        iterator.remove();
1680      }
1681    }
1682
1683
1684    // Any remaining attribute types should exist in both servers.  Compare them
1685    // and see if there are any differences between them.
1686    for (final OID oid : attributeTypes1.keySet())
1687    {
1688      final AttributeTypeDefinition d1 = attributeTypes1.get(oid);
1689      final AttributeTypeDefinition d2 = attributeTypes2.get(oid);
1690
1691      final String identifier = compareNames(
1692           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(),
1693           oid.toString(), d1.getNames(), d2.getNames(), numDifferences);
1694
1695      compareStringValues(
1696           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier,
1697           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_SUPERIOR_TYPE.get(),
1698           d1.getSuperiorType(), d2.getSuperiorType(), numDifferences);
1699
1700      compareStringValues(
1701           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier,
1702           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_SYNTAX_OID.get(),
1703           d1.getSyntaxOID(), d2.getSyntaxOID(), numDifferences);
1704
1705      compareStringValues(
1706           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier,
1707           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_EQUALITY_MR.get(),
1708           d1.getEqualityMatchingRule(), d2.getEqualityMatchingRule(),
1709           numDifferences);
1710
1711      compareStringValues(
1712           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier,
1713           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_ORDERING_MR.get(),
1714           d1.getOrderingMatchingRule(), d2.getOrderingMatchingRule(),
1715           numDifferences);
1716
1717      compareStringValues(
1718           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier,
1719           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_SUBSTRING_MR.get(),
1720           d1.getSubstringMatchingRule(), d2.getSubstringMatchingRule(),
1721           numDifferences);
1722
1723      compareBooleanValues(
1724           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier,
1725           INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_SINGLE_VALUE.get(),
1726           d1.isSingleValued(), d2.isSingleValued(), numDifferences);
1727
1728      compareStringValues(
1729           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier,
1730           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_USAGE.get(),
1731           d1.getUsage().getName(), d2.getUsage().getName(),
1732           numDifferences);
1733
1734      compareBooleanValues(
1735           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier,
1736           INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_NO_USER_MOD.get(),
1737           d1.isNoUserModification(), d2.isNoUserModification(),
1738           numDifferences);
1739
1740      compareBooleanValues(
1741           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier,
1742           INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_COLLECTIVE.get(),
1743           d1.isCollective(), d2.isCollective(), numDifferences);
1744
1745      compareBooleanValues(
1746           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier,
1747           INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_OBSOLETE.get(),
1748           d1.isObsolete(), d2.isObsolete(), numDifferences);
1749
1750      if (! ignoreDescriptions)
1751      {
1752        compareStringValues(
1753             INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_TYPE.get(), identifier,
1754             INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(),
1755             d1.getDescription(), d2.getDescription(), numDifferences);
1756      }
1757
1758      compareExtensions(
1759           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_ATTRIBUTE_SYNTAX.get(),
1760           identifier, d1.getExtensions(), d2.getExtensions(),
1761           numDifferences);
1762    }
1763  }
1764
1765
1766
1767  /**
1768   * Retrieves a map of the attribute type definitions contained in the provided
1769   * schema, indexed by OID.
1770   *
1771   * @param  schema  The schema from which to retrieve the attribute types.  It
1772   *                 must not be {@code null}.
1773   *
1774   * @return  A map of the attribute type definitions contained in the provided
1775   *          schema.
1776   */
1777  @NotNull()
1778  private Map<OID,AttributeTypeDefinition> getAttributeTypeMap(
1779               @NotNull final Schema schema)
1780  {
1781    final Map<OID,AttributeTypeDefinition> attributeTypes = new TreeMap<>();
1782    for (final AttributeTypeDefinition d : schema.getAttributeTypes())
1783    {
1784      if (includeBasedOnNameAndExtensions(d.getNames(), d.getExtensions()))
1785      {
1786        attributeTypes.put(new OID(StaticUtils.toLowerCase(d.getOID())), d);
1787      }
1788    }
1789
1790    return attributeTypes;
1791  }
1792
1793
1794
1795  /**
1796   * Compares the object class definitions contained in the provided schemas.
1797   *
1798   * @param  firstServerSchema   The schema retrieved from the first server.  It
1799   *                             must not be {@code null}.
1800   * @param  secondServerSchema  The schema retrieved from the second server.
1801   *                             It must not be {@code null}.
1802   * @param  numDifferences      A counter used to keep track of the number of
1803   *                             differences found between the schemas.  It must
1804   *                             not be {@code null}.
1805   */
1806  private void compareObjectClasses(
1807                    @NotNull final Schema firstServerSchema,
1808                    @NotNull final Schema secondServerSchema,
1809                    @NotNull final AtomicInteger numDifferences)
1810  {
1811    // Get the object class definitions from each of the schemas.
1812    final Map<OID,ObjectClassDefinition> objectClasses1 =
1813         getObjectClassMap(firstServerSchema);
1814    final Map<OID,ObjectClassDefinition> objectClasses2 =
1815         getObjectClassMap(secondServerSchema);
1816
1817
1818    // Identify object classes that exist in one server but not another.  If
1819    // any are found, then report them and remove them from the set.
1820    Iterator<Map.Entry<OID,ObjectClassDefinition>> iterator =
1821         objectClasses1.entrySet().iterator();
1822    while (iterator.hasNext())
1823    {
1824      final Map.Entry<OID,ObjectClassDefinition> e = iterator.next();
1825      final OID oid = e.getKey();
1826      if (! objectClasses2.containsKey(oid))
1827      {
1828        reportDifference(
1829             WARN_COMPARE_SCHEMA_MISSING_OBJECT_CLASS.get(
1830                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
1831                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
1832             numDifferences,
1833             e.getValue().toString());
1834        iterator.remove();
1835      }
1836    }
1837
1838    iterator = objectClasses2.entrySet().iterator();
1839    while (iterator.hasNext())
1840    {
1841      final Map.Entry<OID,ObjectClassDefinition> e = iterator.next();
1842      final OID oid = e.getKey();
1843      if (! objectClasses1.containsKey(oid))
1844      {
1845        reportDifference(
1846             WARN_COMPARE_SCHEMA_MISSING_OBJECT_CLASS.get(
1847                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
1848                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
1849             numDifferences,
1850             e.getValue().toString());
1851        iterator.remove();
1852      }
1853    }
1854
1855
1856    // Any remaining object classes should exist in both servers.  Compare them
1857    // and see if there are any differences between them.
1858    for (final OID oid : objectClasses1.keySet())
1859    {
1860      final ObjectClassDefinition d1 = objectClasses1.get(oid);
1861      final ObjectClassDefinition d2 = objectClasses2.get(oid);
1862
1863      final String identifier = compareNames(
1864           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(),
1865           oid.toString(), d1.getNames(), d2.getNames(), numDifferences);
1866
1867      compareStringArrayValues(
1868           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(), identifier,
1869           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_SUPERIOR_TYPE.get(),
1870           d1.getSuperiorClasses(), d2.getSuperiorClasses(), numDifferences);
1871
1872      compareStringArrayValues(
1873           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(), identifier,
1874           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_REQUIRED_ATTRIBUTE.get(),
1875           d1.getRequiredAttributes(), d2.getRequiredAttributes(),
1876           numDifferences);
1877
1878      compareStringArrayValues(
1879           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(), identifier,
1880           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_OPTIONAL_ATTRIBUTE.get(),
1881           d1.getOptionalAttributes(), d2.getOptionalAttributes(),
1882           numDifferences);
1883
1884      final String oc1Type = (d1.getObjectClassType() == null)
1885           ? null
1886           : d1.getObjectClassType().getName();
1887      final String oc2Type = (d2.getObjectClassType() == null)
1888           ? null
1889           : d2.getObjectClassType().getName();
1890      compareStringValues(
1891           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(), identifier,
1892           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_OBJECT_CLASS_TYPE.get(),
1893           oc1Type, oc2Type, numDifferences);
1894
1895      compareBooleanValues(
1896           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(), identifier,
1897           INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_OBSOLETE.get(),
1898           d1.isObsolete(), d2.isObsolete(), numDifferences);
1899
1900      if (! ignoreDescriptions)
1901      {
1902        compareStringValues(
1903             INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(), identifier,
1904             INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(),
1905             d1.getDescription(), d2.getDescription(), numDifferences);
1906      }
1907
1908      compareExtensions(
1909           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_OBJECT_CLASS.get(),
1910           identifier, d1.getExtensions(), d2.getExtensions(),
1911           numDifferences);
1912    }
1913  }
1914
1915
1916
1917  /**
1918   * Retrieves a map of the object class definitions contained in the provided
1919   * schema, indexed by OID.
1920   *
1921   * @param  schema  The schema from which to retrieve the object classes.  It
1922   *                 must not be {@code null}.
1923   *
1924   * @return  A map of the object class definitions contained in the provided
1925   *          schema.
1926   */
1927  @NotNull()
1928  private Map<OID,ObjectClassDefinition> getObjectClassMap(
1929               @NotNull final Schema schema)
1930  {
1931    final Map<OID,ObjectClassDefinition> objectClasses = new TreeMap<>();
1932    for (final ObjectClassDefinition d : schema.getObjectClasses())
1933    {
1934      if (includeBasedOnNameAndExtensions(d.getNames(), d.getExtensions()))
1935      {
1936        objectClasses.put(new OID(StaticUtils.toLowerCase(d.getOID())), d);
1937      }
1938    }
1939
1940    return objectClasses;
1941  }
1942
1943
1944
1945  /**
1946   * Compares the DIT content rule definitions contained in the provided
1947   * schemas.
1948   *
1949   * @param  firstServerSchema   The schema retrieved from the first server.  It
1950   *                             must not be {@code null}.
1951   * @param  secondServerSchema  The schema retrieved from the second server.
1952   *                             It must not be {@code null}.
1953   * @param  numDifferences      A counter used to keep track of the number of
1954   *                             differences found between the schemas.  It must
1955   *                             not be {@code null}.
1956   */
1957  private void compareDITContentRules(
1958                    @NotNull final Schema firstServerSchema,
1959                    @NotNull final Schema secondServerSchema,
1960                    @NotNull final AtomicInteger numDifferences)
1961  {
1962    // Get the DIT content rule definitions from each of the schemas.
1963    final Map<OID,DITContentRuleDefinition> ditContentRules1 =
1964         getDITContentRuleMap(firstServerSchema);
1965    final Map<OID,DITContentRuleDefinition> ditContentRules2 =
1966         getDITContentRuleMap(secondServerSchema);
1967
1968
1969    // Identify DIT content rules that exist in one server but not another.  If
1970    // any are found, then report them and remove them from the set.
1971    Iterator<Map.Entry<OID,DITContentRuleDefinition>> iterator =
1972         ditContentRules1.entrySet().iterator();
1973    while (iterator.hasNext())
1974    {
1975      final Map.Entry<OID,DITContentRuleDefinition> e = iterator.next();
1976      final OID oid = e.getKey();
1977      if (! ditContentRules2.containsKey(oid))
1978      {
1979        reportDifference(
1980             WARN_COMPARE_SCHEMA_MISSING_DIT_CONTENT_RULE.get(
1981                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
1982                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
1983             numDifferences,
1984             e.getValue().toString());
1985        iterator.remove();
1986      }
1987    }
1988
1989    iterator = ditContentRules2.entrySet().iterator();
1990    while (iterator.hasNext())
1991    {
1992      final Map.Entry<OID,DITContentRuleDefinition> e = iterator.next();
1993      final OID oid = e.getKey();
1994      if (! ditContentRules1.containsKey(oid))
1995      {
1996        reportDifference(
1997             WARN_COMPARE_SCHEMA_MISSING_DIT_CONTENT_RULE.get(
1998                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
1999                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
2000             numDifferences,
2001             e.getValue().toString());
2002        iterator.remove();
2003      }
2004    }
2005
2006
2007    // Any remaining DIT content rules should exist in both servers.  Compare
2008    // them and see if there are any differences between them.
2009    for (final OID oid : ditContentRules1.keySet())
2010    {
2011      final DITContentRuleDefinition d1 = ditContentRules1.get(oid);
2012      final DITContentRuleDefinition d2 = ditContentRules2.get(oid);
2013
2014      final String identifier = compareNames(
2015           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(),
2016           oid.toString(), d1.getNames(), d2.getNames(), numDifferences);
2017
2018      compareStringArrayValues(
2019           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(), identifier,
2020           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_REQUIRED_ATTRIBUTE.get(),
2021           d1.getRequiredAttributes(), d2.getRequiredAttributes(),
2022           numDifferences);
2023
2024      compareStringArrayValues(
2025           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(), identifier,
2026           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_OPTIONAL_ATTRIBUTE.get(),
2027           d1.getOptionalAttributes(), d2.getOptionalAttributes(),
2028           numDifferences);
2029
2030      compareStringArrayValues(
2031           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(), identifier,
2032           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_PROHIBITED_ATTRIBUTE.get(),
2033           d1.getProhibitedAttributes(), d2.getProhibitedAttributes(),
2034           numDifferences);
2035
2036      compareStringArrayValues(
2037           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(), identifier,
2038           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_AUXILIARY_CLASS.get(),
2039           d1.getAuxiliaryClasses(), d2.getAuxiliaryClasses(), numDifferences);
2040
2041      compareBooleanValues(
2042           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(), identifier,
2043           INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_OBSOLETE.get(),
2044           d1.isObsolete(), d2.isObsolete(), numDifferences);
2045
2046      if (! ignoreDescriptions)
2047      {
2048        compareStringValues(
2049             INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(),
2050             identifier,
2051             INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(),
2052             d1.getDescription(), d2.getDescription(), numDifferences);
2053      }
2054
2055      compareExtensions(
2056           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_CONTENT_RULE.get(),
2057           identifier, d1.getExtensions(), d2.getExtensions(),
2058           numDifferences);
2059    }
2060  }
2061
2062
2063
2064  /**
2065   * Retrieves a map of the DIT content rule definitions contained in the
2066   * provided schema, indexed by OID.
2067   *
2068   * @param  schema  The schema from which to retrieve the DIT content rules.
2069   *                 It must not be {@code null}.
2070   *
2071   * @return  A map of the DIT content rule definitions contained in the
2072   *          provided schema.
2073   */
2074  @NotNull()
2075  private Map<OID,DITContentRuleDefinition> getDITContentRuleMap(
2076               @NotNull final Schema schema)
2077  {
2078    final Map<OID,DITContentRuleDefinition> ditContentRules = new TreeMap<>();
2079    for (final DITContentRuleDefinition d : schema.getDITContentRules())
2080    {
2081      if (includeBasedOnNameAndExtensions(d.getNames(), d.getExtensions()))
2082      {
2083        ditContentRules.put(new OID(StaticUtils.toLowerCase(d.getOID())), d);
2084      }
2085    }
2086
2087    return ditContentRules;
2088  }
2089
2090
2091
2092  /**
2093   * Compares the DIT structure rule definitions contained in the provided
2094   * schemas.
2095   *
2096   * @param  firstServerSchema   The schema retrieved from the first server.  It
2097   *                             must not be {@code null}.
2098   * @param  secondServerSchema  The schema retrieved from the second server.
2099   *                             It must not be {@code null}.
2100   * @param  numDifferences      A counter used to keep track of the number of
2101   *                             differences found between the schemas.  It must
2102   *                             not be {@code null}.
2103   */
2104  private void compareDITStructureRules(
2105                    @NotNull final Schema firstServerSchema,
2106                    @NotNull final Schema secondServerSchema,
2107                    @NotNull final AtomicInteger numDifferences)
2108  {
2109    // Get the DIT structure rule definitions from each of the schemas.
2110    final Map<Integer,DITStructureRuleDefinition> ditStructureRules1 =
2111         getDITStructureRuleMap(firstServerSchema);
2112    final Map<Integer,DITStructureRuleDefinition> ditStructureRules2 =
2113         getDITStructureRuleMap(secondServerSchema);
2114
2115
2116    // Identify DIT structure rules that exist in one server but not another.
2117    // If any are found, then report them and remove them from the set.
2118    Iterator<Map.Entry<Integer,DITStructureRuleDefinition>> iterator =
2119         ditStructureRules1.entrySet().iterator();
2120    while (iterator.hasNext())
2121    {
2122      final Map.Entry<Integer,DITStructureRuleDefinition> e = iterator.next();
2123      final Integer id = e.getKey();
2124      if (! ditStructureRules2.containsKey(id))
2125      {
2126        reportDifference(
2127             WARN_COMPARE_SCHEMA_MISSING_DIT_STRUCTURE_RULE.get(
2128                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
2129                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
2130             numDifferences,
2131             e.getValue().toString());
2132        iterator.remove();
2133      }
2134    }
2135
2136    iterator = ditStructureRules2.entrySet().iterator();
2137    while (iterator.hasNext())
2138    {
2139      final Map.Entry<Integer,DITStructureRuleDefinition> e = iterator.next();
2140      final Integer oid = e.getKey();
2141      if (! ditStructureRules1.containsKey(oid))
2142      {
2143        reportDifference(
2144             WARN_COMPARE_SCHEMA_MISSING_DIT_STRUCTURE_RULE.get(
2145                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
2146                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
2147             numDifferences,
2148             e.getValue().toString());
2149        iterator.remove();
2150      }
2151    }
2152
2153
2154    // Any remaining DIT structure rules should exist in both servers.  Compare
2155    // them and see if there are any differences between them.
2156    for (final Integer id : ditStructureRules1.keySet())
2157    {
2158      final DITStructureRuleDefinition d1 = ditStructureRules1.get(id);
2159      final DITStructureRuleDefinition d2 = ditStructureRules2.get(id);
2160
2161      final String identifier = compareNames(
2162           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULE.get(),
2163           id.toString(), d1.getNames(), d2.getNames(), numDifferences);
2164
2165      compareStringValues(
2166           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULE.get(),
2167           identifier, INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_NAME_FORM.get(),
2168           d1.getNameFormID(), d2.getNameFormID(), numDifferences);
2169
2170      final String[] superiorRuleIDs1 =
2171           intArrayToStringArray(d1.getSuperiorRuleIDs());
2172      final String[] superiorRuleIDs2 =
2173           intArrayToStringArray(d2.getSuperiorRuleIDs());
2174      compareStringArrayValues(
2175           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULE.get(),
2176           identifier,
2177           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_SUPERIOR_RULE_ID.get(),
2178           superiorRuleIDs1, superiorRuleIDs2, numDifferences);
2179
2180      compareBooleanValues(
2181           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULE.get(),
2182           identifier, INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_OBSOLETE.get(),
2183           d1.isObsolete(), d2.isObsolete(), numDifferences);
2184
2185      if (! ignoreDescriptions)
2186      {
2187      compareStringValues(
2188           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULE.get(),
2189           identifier, INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(),
2190           d1.getDescription(), d2.getDescription(), numDifferences);
2191      }
2192
2193      compareExtensions(
2194           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_DIT_STRUCTURE_RULE.get(),
2195           identifier, d1.getExtensions(), d2.getExtensions(),
2196           numDifferences);
2197    }
2198  }
2199
2200
2201
2202  /**
2203   * Retrieves a map of the DIT structure rule definitions contained in the
2204   * provided schema, indexed by rule ID.
2205   *
2206   * @param  schema  The schema from which to retrieve the DIT structure rules.
2207   *                 It must not be {@code null}.
2208   *
2209   * @return  A map of the DIT structure rule definitions contained in the
2210   *          provided schema.
2211   */
2212  @NotNull()
2213  private Map<Integer,DITStructureRuleDefinition> getDITStructureRuleMap(
2214               @NotNull final Schema schema)
2215  {
2216    final Map<Integer,DITStructureRuleDefinition> ditStructureRules =
2217         new TreeMap<>();
2218    for (final DITStructureRuleDefinition d : schema.getDITStructureRules())
2219    {
2220      if (includeBasedOnNameAndExtensions(StaticUtils.NO_STRINGS,
2221           d.getExtensions()))
2222      {
2223        ditStructureRules.put(d.getRuleID(), d);
2224      }
2225    }
2226
2227    return ditStructureRules;
2228  }
2229
2230
2231
2232  /**
2233   * Converts the provided integer array to a string array in which each element
2234   * is the string representation of the corresponding element in the provided
2235   * integer array.
2236   *
2237   * @param  intArray  The integer array to convert to a string array.  It must
2238   *                   not be {@code null}, but may be empty.
2239   *
2240   * @return  A string array in which each element is the string representation
2241   *          of the corresponding element in the provided integer array.
2242   */
2243  @NotNull()
2244  private static String[] intArrayToStringArray(@NotNull final int[] intArray)
2245  {
2246    final String[] stringArray = new String[intArray.length];
2247    for (int i=0; i < intArray.length; i++)
2248    {
2249      stringArray[i] = String.valueOf(intArray[i]);
2250    }
2251
2252    return stringArray;
2253  }
2254
2255
2256
2257  /**
2258   * Compares the name form definitions contained in the provided schemas.
2259   *
2260   * @param  firstServerSchema   The schema retrieved from the first server.  It
2261   *                             must not be {@code null}.
2262   * @param  secondServerSchema  The schema retrieved from the second server.
2263   *                             It must not be {@code null}.
2264   * @param  numDifferences      A counter used to keep track of the number of
2265   *                             differences found between the schemas.  It must
2266   *                             not be {@code null}.
2267   */
2268  private void compareNameForms(
2269                    @NotNull final Schema firstServerSchema,
2270                    @NotNull final Schema secondServerSchema,
2271                    @NotNull final AtomicInteger numDifferences)
2272  {
2273    // Get the name form definitions from each of the schemas.
2274    final Map<OID,NameFormDefinition> nameForms1 =
2275         getNameFormMap(firstServerSchema);
2276    final Map<OID,NameFormDefinition> nameForms2 =
2277         getNameFormMap(secondServerSchema);
2278
2279
2280    // Identify name forms that exist in one server but not another.  If
2281    // any are found, then report them and remove them from the set.
2282    Iterator<Map.Entry<OID,NameFormDefinition>> iterator =
2283         nameForms1.entrySet().iterator();
2284    while (iterator.hasNext())
2285    {
2286      final Map.Entry<OID,NameFormDefinition> e = iterator.next();
2287      final OID oid = e.getKey();
2288      if (! nameForms2.containsKey(oid))
2289      {
2290        reportDifference(
2291             WARN_COMPARE_SCHEMA_MISSING_NAME_FORM.get(
2292                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
2293                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
2294             numDifferences,
2295             e.getValue().toString());
2296        iterator.remove();
2297      }
2298    }
2299
2300    iterator = nameForms2.entrySet().iterator();
2301    while (iterator.hasNext())
2302    {
2303      final Map.Entry<OID,NameFormDefinition> e = iterator.next();
2304      final OID oid = e.getKey();
2305      if (! nameForms1.containsKey(oid))
2306      {
2307        reportDifference(
2308             WARN_COMPARE_SCHEMA_MISSING_NAME_FORM.get(
2309                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
2310                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
2311             numDifferences,
2312             e.getValue().toString());
2313        iterator.remove();
2314      }
2315    }
2316
2317
2318    // Any remaining name forms should exist in both servers.  Compare them and
2319    // see if there are any differences between them.
2320    for (final OID oid : nameForms1.keySet())
2321    {
2322      final NameFormDefinition d1 = nameForms1.get(oid);
2323      final NameFormDefinition d2 = nameForms2.get(oid);
2324
2325      final String identifier = compareNames(
2326           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get(),
2327           oid.toString(), d1.getNames(), d2.getNames(), numDifferences);
2328
2329      compareStringValues(
2330           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get(), identifier,
2331           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_STRUCTURAL_CLASS.get(),
2332           d1.getStructuralClass(), d2.getStructuralClass(), numDifferences);
2333
2334      compareStringArrayValues(
2335           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get(), identifier,
2336           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_REQUIRED_ATTRIBUTE.get(),
2337           d1.getRequiredAttributes(), d2.getRequiredAttributes(),
2338           numDifferences);
2339
2340      compareStringArrayValues(
2341           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get(), identifier,
2342           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_OPTIONAL_ATTRIBUTE.get(),
2343           d1.getOptionalAttributes(), d2.getOptionalAttributes(),
2344           numDifferences);
2345
2346      compareBooleanValues(
2347           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get(), identifier,
2348           INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_OBSOLETE.get(),
2349           d1.isObsolete(), d2.isObsolete(), numDifferences);
2350
2351      if (! ignoreDescriptions)
2352      {
2353        compareStringValues(
2354             INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get(), identifier,
2355             INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(),
2356             d1.getDescription(), d2.getDescription(), numDifferences);
2357      }
2358
2359      compareExtensions(
2360           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_NAME_FORM.get(),
2361           identifier, d1.getExtensions(), d2.getExtensions(),
2362           numDifferences);
2363    }
2364  }
2365
2366
2367
2368  /**
2369   * Retrieves a map of the name form definitions contained in the provided
2370   * schema, indexed by OID.
2371   *
2372   * @param  schema  The schema from which to retrieve the name forms.  It must
2373   *                 not be {@code null}.
2374   *
2375   * @return  A map of the name form definitions contained in the provided
2376   *          schema.
2377   */
2378  @NotNull()
2379  private Map<OID,NameFormDefinition> getNameFormMap(
2380               @NotNull final Schema schema)
2381  {
2382    final Map<OID,NameFormDefinition> nameForms = new TreeMap<>();
2383    for (final NameFormDefinition d : schema.getNameForms())
2384    {
2385      if (includeBasedOnNameAndExtensions(d.getNames(), d.getExtensions()))
2386      {
2387        nameForms.put(new OID(StaticUtils.toLowerCase(d.getOID())), d);
2388      }
2389    }
2390
2391    return nameForms;
2392  }
2393
2394
2395
2396  /**
2397   * Compares the matching rule use definitions contained in the provided
2398   * schemas.
2399   *
2400   * @param  firstServerSchema   The schema retrieved from the first server.  It
2401   *                             must not be {@code null}.
2402   * @param  secondServerSchema  The schema retrieved from the second server.
2403   *                             It must not be {@code null}.
2404   * @param  numDifferences      A counter used to keep track of the number of
2405   *                             differences found between the schemas.  It must
2406   *                             not be {@code null}.
2407   */
2408  private void compareMatchingRuleUses(
2409                    @NotNull final Schema firstServerSchema,
2410                    @NotNull final Schema secondServerSchema,
2411                    @NotNull final AtomicInteger numDifferences)
2412  {
2413    // Get the matching rule use definitions from each of the schemas.
2414    final Map<OID,MatchingRuleUseDefinition> matchingRuleUses1 =
2415         getMatchingRuleUseMap(firstServerSchema);
2416    final Map<OID,MatchingRuleUseDefinition> matchingRuleUses2 =
2417         getMatchingRuleUseMap(secondServerSchema);
2418
2419
2420    // Identify matching rule uses that exist in one server but not another.  If
2421    // any are found, then report them and remove them from the set.
2422    Iterator<Map.Entry<OID,MatchingRuleUseDefinition>> iterator =
2423         matchingRuleUses1.entrySet().iterator();
2424    while (iterator.hasNext())
2425    {
2426      final Map.Entry<OID,MatchingRuleUseDefinition> e = iterator.next();
2427      final OID oid = e.getKey();
2428      if (! matchingRuleUses2.containsKey(oid))
2429      {
2430        reportDifference(
2431             WARN_COMPARE_SCHEMA_MISSING_MATCHING_RULE_USE.get(
2432                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
2433                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
2434             numDifferences,
2435             e.getValue().toString());
2436        iterator.remove();
2437      }
2438    }
2439
2440    iterator = matchingRuleUses2.entrySet().iterator();
2441    while (iterator.hasNext())
2442    {
2443      final Map.Entry<OID,MatchingRuleUseDefinition> e = iterator.next();
2444      final OID oid = e.getKey();
2445      if (! matchingRuleUses1.containsKey(oid))
2446      {
2447        reportDifference(
2448             WARN_COMPARE_SCHEMA_MISSING_MATCHING_RULE_USE.get(
2449                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
2450                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
2451             numDifferences,
2452             e.getValue().toString());
2453        iterator.remove();
2454      }
2455    }
2456
2457
2458    // Any remaining matching rule uses should exist in both servers.  Compare
2459    // them and see if there are any differences between them.
2460    for (final OID oid : matchingRuleUses1.keySet())
2461    {
2462      final MatchingRuleUseDefinition d1 = matchingRuleUses1.get(oid);
2463      final MatchingRuleUseDefinition d2 = matchingRuleUses2.get(oid);
2464
2465      final String identifier = compareNames(
2466           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USE.get(),
2467           oid.toString(), d1.getNames(), d2.getNames(), numDifferences);
2468
2469      compareStringArrayValues(
2470           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USE.get(), identifier,
2471           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_APPLICABLE_ATTRIBUTE.get(),
2472           d1.getApplicableAttributeTypes(), d2.getApplicableAttributeTypes(),
2473           numDifferences);
2474
2475      compareBooleanValues(
2476           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USE.get(), identifier,
2477           INFO_COMPARE_SCHEMA_BOOLEAN_FIELD_NAME_OBSOLETE.get(),
2478           d1.isObsolete(), d2.isObsolete(), numDifferences);
2479
2480      if (! ignoreDescriptions)
2481      {
2482      compareStringValues(
2483           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USE.get(), identifier,
2484           INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_DESCRIPTION.get(),
2485           d1.getDescription(), d2.getDescription(), numDifferences);
2486      }
2487
2488      compareExtensions(
2489           INFO_COMPARE_SCHEMA_ELEMENT_TYPE_MATCHING_RULE_USE.get(),
2490           identifier, d1.getExtensions(), d2.getExtensions(),
2491           numDifferences);
2492    }
2493  }
2494
2495
2496
2497  /**
2498   * Retrieves a map of the matching rule use definitions contained in the
2499   * provided schema, indexed by OID.
2500   *
2501   * @param  schema  The schema from which to retrieve the matching rule uses.
2502   *                 It must not be {@code null}.
2503   *
2504   * @return  A map of the matching rule use definitions contained in the
2505   *          provided schema.
2506   */
2507  @NotNull()
2508  private Map<OID,MatchingRuleUseDefinition> getMatchingRuleUseMap(
2509               @NotNull final Schema schema)
2510  {
2511    final Map<OID,MatchingRuleUseDefinition> matchingRuleUses = new TreeMap<>();
2512    for (final MatchingRuleUseDefinition d : schema.getMatchingRuleUses())
2513    {
2514      if (includeBasedOnNameAndExtensions(d.getNames(), d.getExtensions()))
2515      {
2516        matchingRuleUses.put(new OID(StaticUtils.toLowerCase(d.getOID())), d);
2517      }
2518    }
2519
2520    return matchingRuleUses;
2521  }
2522
2523
2524
2525  /**
2526   * Indicates whether to include a schema element with the given name and set
2527   * of extensions.
2528   *
2529   * @param  names       The set of names for the schema element.  It must not
2530   *                     be {@code null}, but may be empty.
2531   * @param  extensions  The set of extensions for the schema element.  It must
2532   *                     not be {@code null}, but may be empty.
2533   *
2534   * @return  {@code true} if an element with the given names and set of
2535   *          extensions should be included, or {@code false} if not.
2536   */
2537  private boolean includeBasedOnNameAndExtensions(
2538               @NotNull final String[] names,
2539               @NotNull final Map<String,String[]> extensions)
2540  {
2541    if (includeOrExcludeBasedOnName && (names.length > 0))
2542    {
2543      boolean includeFound = false;
2544      for (final String name : names)
2545      {
2546        final String lowerName = StaticUtils.toLowerCase(name);
2547        for (final String excludePrefix : excludeNamePrefixes)
2548        {
2549          if (lowerName.startsWith(excludePrefix))
2550          {
2551            return false;
2552          }
2553        }
2554
2555        if (! includeNamePrefixes.isEmpty())
2556        {
2557          for (final String includePrefix : includeNamePrefixes)
2558          {
2559            if (lowerName.startsWith(includePrefix))
2560            {
2561              includeFound = true;
2562              break;
2563            }
2564          }
2565        }
2566      }
2567
2568      if ((! includeNamePrefixes.isEmpty()) && (! includeFound))
2569      {
2570        return false;
2571      }
2572    }
2573
2574
2575    if (includeOrExcludeBasedOnExtensions && (! extensions.isEmpty()))
2576    {
2577      boolean includeFound = false;
2578      for (final Map.Entry<String,String[]> e : extensions.entrySet())
2579      {
2580        final String lowerName = StaticUtils.toLowerCase(e.getKey());
2581        final String[] values = e.getValue();
2582        final String[] lowerValues = new String[values.length];
2583        for (int i=0; i < values.length; i++)
2584        {
2585          lowerValues[i] = StaticUtils.toLowerCase(values[i]);
2586        }
2587
2588        final List<String> excludeValues =
2589             excludeExtensionValues.get(lowerName);
2590        if (excludeValues != null)
2591        {
2592          for (final String lowerValue : lowerValues)
2593          {
2594            if (excludeValues.contains(lowerValue))
2595            {
2596              return false;
2597            }
2598          }
2599        }
2600
2601        final List<String> includeValues =
2602             includeExtensionValues.get(lowerName);
2603        if (includeValues != null)
2604        {
2605          for (final String lowerValue : lowerValues)
2606          {
2607            if (includeValues.contains(lowerValue))
2608            {
2609              includeFound = true;
2610              break;
2611            }
2612          }
2613        }
2614      }
2615
2616      if ((! includeExtensionValues.isEmpty()) && (! includeFound))
2617      {
2618        return false;
2619      }
2620    }
2621
2622
2623    return true;
2624  }
2625
2626
2627
2628  /**
2629   * Reports a difference between schema elements.
2630   *
2631   * @param  message            The message to display with information about
2632   *                            the difference.  It must not be {@code null}.
2633   * @param  numDifferences     A counter used to keep track of the number of
2634   *                            differences found between the schemas.  It must
2635   *                            not be {@code null}.
2636   * @param  additionalStrings  A set of additional strings that should also be
2637   *                            displayed, in addition to the provided message.
2638   *                            Each additional string will be presented on its
2639   *                            own line without any wrapping.  It must not be
2640   *                            {@code null}, but may be empty.
2641   */
2642  private void reportDifference(@NotNull final String message,
2643                                @NotNull final AtomicInteger numDifferences,
2644                                @NotNull final String... additionalStrings)
2645  {
2646    wrapErr(0, WRAP_COLUMN, message);
2647    for (final String additionalString : additionalStrings)
2648    {
2649      err(additionalString);
2650    }
2651    err();
2652    numDifferences.incrementAndGet();
2653  }
2654
2655
2656
2657  /**
2658   * Identifies differences between string values for two schema elements.
2659   *
2660   * @param  elementTypeName    A name for the type of schema element being
2661   *                            compared.  It must not be {@code null}.
2662   * @param  elementIdentifier  An identifier (e.g., the name or OID) for the
2663   *                            schema element for which to make the
2664   *                            determination.  It must not be {@code null}.
2665   * @param  stringDescriptor   A descriptor for the string values being
2666   *                            compared.
2667   * @param  string1            The string value from the first schema element.
2668   *                            It may be {@code null} if the element does not
2669   *                            contain a value for the associated field.
2670   * @param  string2            The string value from the first second element.
2671   *                            It may be {@code null} if the element does not
2672   *                            contain a value for the associated field.
2673   * @param  numDifferences     A counter used to keep track of the number of
2674   *                            differences found between the schemas.  It must
2675   *                            not be {@code null}.
2676   */
2677  private void compareStringValues(@NotNull final String elementTypeName,
2678                                   @NotNull final String elementIdentifier,
2679                                   @NotNull final String stringDescriptor,
2680                                   @Nullable final String string1,
2681                                   @Nullable final String string2,
2682                                   @NotNull final AtomicInteger numDifferences)
2683  {
2684    if (string1 == null)
2685    {
2686      if (string2 != null)
2687      {
2688        reportDifference(
2689             WARN_COMPARE_SCHEMA_STRING_MISSING_FROM_SERVER.get(
2690                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
2691                  elementTypeName, elementIdentifier, stringDescriptor,
2692                  string2, INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
2693             numDifferences);
2694      }
2695    }
2696    else if (string2 == null)
2697    {
2698      reportDifference(
2699           WARN_COMPARE_SCHEMA_STRING_MISSING_FROM_SERVER.get(
2700                INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
2701                elementTypeName, elementIdentifier, stringDescriptor,
2702                string1, INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
2703           numDifferences);
2704    }
2705    else if (! string1.equalsIgnoreCase(string2))
2706    {
2707      reportDifference(
2708           WARN_COMPARE_SCHEMA_STRING_DIFFERENT_BETWEEN_SERVERS.get(
2709                elementTypeName, elementIdentifier, stringDescriptor, string1,
2710                string2),
2711           numDifferences);
2712    }
2713  }
2714
2715
2716
2717  /**
2718   * Identifies differences between the sets of string arrays for two schema
2719   * elements.
2720   *
2721   * @param  elementTypeName    A name for the type of schema element being
2722   *                            compared.  It must not be {@code null}.
2723   * @param  elementIdentifier  An identifier (e.g., the name or OID) for the
2724   *                            schema element for which to make the
2725   *                            determination.  It must not be {@code null}.
2726   * @param  stringDescriptor   A descriptor for the string values being
2727   *                            compared.
2728   * @param  array1             The array of values for the target field from
2729   *                            the element in the first schema.  It must not be
2730   *                            {@code null}, but may be empty.
2731   * @param  array2             The array of values for the target field from
2732   *                            the element in the second schema.  It must not
2733   *                            be {@code null}, but may be empty.
2734   * @param  numDifferences     A counter used to keep track of the number of
2735   *                            differences found between the schemas.  It must
2736   *                            not be {@code null}.
2737   */
2738  private void compareStringArrayValues(
2739                    @NotNull final String elementTypeName,
2740                    @NotNull final String elementIdentifier,
2741                    @NotNull final String stringDescriptor,
2742                    @NotNull final String[] array1,
2743                    @NotNull final String[] array2,
2744                    @NotNull final AtomicInteger numDifferences)
2745  {
2746    if (array1.length == 0)
2747    {
2748      switch (array2.length)
2749      {
2750        case 0:
2751          // The element doesn't have any names in either of the servers, so
2752          // there is no difference.
2753          break;
2754        case 1:
2755          // The element has names in the second server, but not in the first.
2756          reportDifference(
2757               WARN_COMPARE_SCHEMA_STRING_ARRAY_SINGLE_IN_ONLY_ONE_SERVER.get(
2758                    INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
2759                    elementTypeName, elementIdentifier, stringDescriptor,
2760                    array2[0], INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
2761               numDifferences);
2762          break;
2763        default:
2764          reportDifference(
2765               WARN_COMPARE_SCHEMA_STRING_ARRAY_MULTIPLE_IN_ONLY_ONE_SERVER.get(
2766                    INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
2767                    elementTypeName, elementIdentifier, stringDescriptor,
2768                    Arrays.toString(array2),
2769                    INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
2770               numDifferences);
2771          break;
2772      }
2773    }
2774    else if (array2.length == 0)
2775    {
2776      // The element has names in the first server, but not in the second.
2777      if (array1.length == 1)
2778      {
2779        reportDifference(
2780             WARN_COMPARE_SCHEMA_STRING_ARRAY_SINGLE_IN_ONLY_ONE_SERVER.get(
2781                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
2782                  elementTypeName, elementIdentifier, stringDescriptor,
2783                  array1[0], INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
2784             numDifferences);
2785      }
2786      else
2787      {
2788        reportDifference(
2789             WARN_COMPARE_SCHEMA_STRING_ARRAY_MULTIPLE_IN_ONLY_ONE_SERVER.get(
2790                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
2791                  elementTypeName, elementIdentifier, stringDescriptor,
2792                  Arrays.toString(array1),
2793                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
2794             numDifferences);
2795      }
2796    }
2797    else
2798    {
2799      // The element has names in both servers.  See if there are any
2800      // differences between them.
2801      final Map<String,String> n1 = getNameMap(array1);
2802      final Map<String,String> n2 = getNameMap(array2);
2803      for (final Map.Entry<String,String> e : n1.entrySet())
2804      {
2805        final String lowerName = e.getKey();
2806        if (n2.remove(lowerName) == null)
2807        {
2808          reportDifference(
2809               WARN_COMPARE_SCHEMA_STRING_ARRAY_VALUE_MISSING_FROM_SERVER.get(
2810                    elementTypeName, elementIdentifier, stringDescriptor,
2811                    INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
2812                    e.getValue(),
2813                    INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
2814               numDifferences);
2815        }
2816      }
2817
2818      for (final String nameOnlyInServer2 : n2.values())
2819      {
2820        reportDifference(
2821             WARN_COMPARE_SCHEMA_STRING_ARRAY_VALUE_MISSING_FROM_SERVER.get(
2822                  elementTypeName, elementIdentifier, stringDescriptor,
2823                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
2824                  nameOnlyInServer2,
2825                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
2826             numDifferences);
2827      }
2828    }
2829  }
2830
2831
2832
2833  /**
2834   * Identifies differences between the sets of names for two schema elements.
2835   *
2836   * @param  elementTypeName    A name for the type of schema element being
2837   *                            compared.  It must not be {@code null}.
2838   * @param  elementIdentifier  An identifier (e.g., the name or OID) for the
2839   *                            schema element for which to make the
2840   *                            determination.  It must not be {@code null}.
2841   * @param  names1             The set of names for the element from the first
2842   *                            schema.  It must not be {@code null}, but may be
2843   *                            empty.
2844   * @param  names2             The set of names for the element from the second
2845   *                            schema.  It must not be {@code null}, but may be
2846   *                            empty.
2847   * @param  numDifferences     A counter used to keep track of the number of
2848   *                            differences found between the schemas.  It must
2849   *                            not be {@code null}.
2850   *
2851   * @return  The identifier string that should be used to identify the
2852   *          associated schema element.  If both sets of names are non-empty
2853   *          and have the same first element, then that name will be used as
2854   *          the identifier.  Otherwise, the provided identifier will be used.
2855   */
2856  @NotNull()
2857  private String compareNames(@NotNull final String elementTypeName,
2858                              @NotNull final String elementIdentifier,
2859                              @NotNull final String[] names1,
2860                              @NotNull final String[] names2,
2861                              @NotNull final AtomicInteger numDifferences)
2862  {
2863    compareStringArrayValues(elementTypeName, elementIdentifier,
2864         INFO_COMPARE_SCHEMA_STRING_DESCRIPTOR_NAME.get(), names1, names2,
2865         numDifferences);
2866
2867
2868    // Identify the best identifier to use for the schema element going forward.
2869    if ((names1.length > 0) && (names2.length > 0) &&
2870         (names1[0].equalsIgnoreCase(names2[0])))
2871    {
2872      return names1[0];
2873    }
2874    else
2875    {
2876      return elementIdentifier;
2877    }
2878  }
2879
2880
2881
2882  /**
2883   * Identifies difference between boolean values for two schema elements.
2884   *
2885   * @param  elementTypeName    A name for the type of schema element being
2886   *                            compared.  It must not be {@code null}.
2887   * @param  elementIdentifier  An identifier (e.g., the name or OID) for the
2888   *                            schema element for which to make the
2889   *                            determination.  It must not be {@code null}.
2890   * @param  booleanFieldName   The name of the Boolean field being compared.
2891   * @param  value1             The Boolean value from the first schema element.
2892   * @param  value2             The Boolean value from the second schema
2893   *                            element.
2894   * @param  numDifferences     A counter used to keep track of the number of
2895   *                            differences found between the schemas.  It must
2896   *                            not be {@code null}.
2897   */
2898  private void compareBooleanValues(@NotNull final String elementTypeName,
2899                                    @NotNull final String elementIdentifier,
2900                                    @NotNull final String booleanFieldName,
2901                                    final boolean value1,
2902                                    final boolean value2,
2903                                    @NotNull final AtomicInteger numDifferences)
2904  {
2905    if (value1 != value2)
2906    {
2907      if (value1)
2908      {
2909        reportDifference(
2910             WARN_COMPARE_SCHEMA_BOOLEAN_DIFFERENCE.get(
2911                  elementTypeName, elementIdentifier, booleanFieldName,
2912                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
2913                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
2914             numDifferences);
2915      }
2916      else
2917      {
2918        reportDifference(
2919             WARN_COMPARE_SCHEMA_BOOLEAN_DIFFERENCE.get(
2920                  elementTypeName, elementIdentifier, booleanFieldName,
2921                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
2922                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
2923             numDifferences);
2924      }
2925    }
2926  }
2927
2928
2929
2930  /**
2931   * Identifies differences between the sets of extensions for two schema
2932   * elements.
2933   *
2934   * @param  elementTypeName    A name for the type of schema element being
2935   *                            compared.  It must not be {@code null}.
2936   * @param  elementIdentifier  An identifier (e.g., the name or OID) for the
2937   *                            schema element for which to make the
2938   *                            determination.  It must not be {@code null}.
2939   * @param  extensions1        The set of extensions for the element from the
2940   *                            first schema.  It must not be {@code null}, but
2941   *                            may be empty.
2942   * @param  extensions2        The set of extensions for the element from the
2943   *                            second schema.  It must not be {@code null}, but
2944   *                            may be empty.
2945   * @param  numDifferences     A counter used to keep track of the number of
2946   *                            differences found between the schemas.  It must
2947   *                            not be {@code null}.
2948   */
2949  private void compareExtensions(
2950                    @NotNull final String elementTypeName,
2951                    @NotNull final String elementIdentifier,
2952                    @NotNull final Map<String,String[]> extensions1,
2953                    @NotNull final Map<String,String[]> extensions2,
2954                    @NotNull final AtomicInteger numDifferences)
2955  {
2956    if (ignoreExtensions)
2957    {
2958      return;
2959    }
2960
2961
2962    // Convert the extensions into a map of sets so that we can alter the
2963    // contents of both the map and its sets.
2964    final Map<String,Set<String>> e1 =
2965         convertToUpdatableExtensionsMap(extensions1);
2966    final Map<String,Set<String>> e2 =
2967         convertToUpdatableExtensionsMap(extensions2);
2968
2969
2970    // Iterate through the extensions and identify differences between them.
2971    for (final Map.Entry<String,Set<String>> e : e1.entrySet())
2972    {
2973      final String extensionName = e.getKey();
2974      final Set<String> extension1Values = e.getValue();
2975      final Set<String> extension2Values = e2.remove(extensionName);
2976      if (extension2Values == null)
2977      {
2978        reportDifference(
2979             WARN_COMPARE_SCHEMA_MISSING_EXTENSION.get(
2980                  INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get(),
2981                  elementTypeName, elementIdentifier, extensionName,
2982                  INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get()),
2983             numDifferences);
2984      }
2985      else if (! extension1Values.equals(extension2Values))
2986      {
2987        reportDifference(
2988             WARN_COMPARE_SCHEMA_EXTENSION_DIFFERENCE.get(
2989                  elementTypeName, elementIdentifier, extensionName),
2990             numDifferences);
2991      }
2992    }
2993
2994    for (final String extensionName : e2.keySet())
2995    {
2996      reportDifference(
2997           WARN_COMPARE_SCHEMA_MISSING_EXTENSION.get(
2998                INFO_COMPARE_SCHEMA_SECOND_SERVER_LABEL.get(),
2999                elementTypeName, elementIdentifier, extensionName,
3000                INFO_COMPARE_SCHEMA_FIRST_SERVER_LABEL.get()),
3001           numDifferences);
3002    }
3003  }
3004
3005
3006
3007  /**
3008   * Converts the provided extensions map into an updatable map that associates
3009   * each extension name key with a modifiable set of values rather than an
3010   * array.  In addition, all strings will be converted to lowercase for more
3011   * efficient case-insensitive comparison.
3012   *
3013   * @param  extensionsMap  The map to be converted.  It must not be
3014   *                        {@code null}, but may be empty.
3015   *
3016   * @return  A modifiable map that contains the information in the provided map
3017   *          in a form that is better suited for comparing extensions between
3018   *          two definitions.
3019   */
3020  @NotNull()
3021  private static Map<String,Set<String>> convertToUpdatableExtensionsMap(
3022               @NotNull final Map<String,String[]> extensionsMap)
3023  {
3024    final Map<String,Set<String>> convertedExtensionsMap = new TreeMap<>();
3025    for (final Map.Entry<String,String[]> e : extensionsMap.entrySet())
3026    {
3027      final String lowerExtensionName = StaticUtils.toLowerCase(e.getKey());
3028      final Set<String> lowerExtensionValues = new TreeSet<>();
3029      for (final String extensionValue : e.getValue())
3030      {
3031        lowerExtensionValues.add(StaticUtils.toLowerCase(extensionValue));
3032      }
3033
3034      convertedExtensionsMap.put(lowerExtensionName, lowerExtensionValues);
3035    }
3036
3037    return convertedExtensionsMap;
3038  }
3039
3040
3041
3042  /**
3043   * Retrieves a modifiable map containing the provided names.  The key for each
3044   * entry in the map will be the name in all lowercase, and the value will be
3045   * the name in the case in which it is provided.
3046   *
3047   * @param  names  The names to include in the resulting map.  It must not be
3048   *                {@code null}.
3049   *
3050   * @return  A modifiable map containing the provided names.
3051   */
3052  @NotNull()
3053  private static Map<String,String> getNameMap(@NotNull final String[] names)
3054  {
3055    final Map<String,String> m = new TreeMap<>();
3056    for (final String name : names)
3057    {
3058      m.put(StaticUtils.toLowerCase(name), name);
3059    }
3060
3061    return m;
3062  }
3063
3064
3065
3066  /**
3067   * Logs the provided message to standard error and sets it as the tool
3068   * completion message.
3069   *
3070   * @param  message  The completion message.  It must not be {@code null}.
3071   */
3072  private void logCompletionError(@NotNull final String message)
3073  {
3074    completionMessageRef.compareAndSet(null, message);
3075    wrapErr(0, WRAP_COLUMN, message);
3076  }
3077
3078
3079
3080  /**
3081   * {@inheritDoc}
3082   */
3083  @Override()
3084  @NotNull()
3085  public LinkedHashMap<String[],String> getExampleUsages()
3086  {
3087    final LinkedHashMap<String[],String> examples = new LinkedHashMap<>();
3088
3089    examples.put(
3090         new String[]
3091         {
3092           "--firstHostname", "ds1.example.com",
3093           "--firstPort", "636",
3094           "--firstUseSSL",
3095           "--firstBindDN", "cn=Directory Manager",
3096           "--firstBindPasswordFile", "/path/to/password.txt",
3097           "--secondHostname", "ds2.example.com",
3098           "--secondPort", "636",
3099           "--secondUseSSL",
3100           "--secondBindDN", "cn=Directory Manager",
3101           "--secondBindPasswordFile", "/path/to/password.txt"
3102         },
3103         INFO_COMPARE_LDAP_SCHEMAS_EXAMPLE.get());
3104
3105    return examples;
3106  }
3107}