001/*
002 * Copyright 2020-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2020-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) 2020-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.Arrays;
042import java.util.Collection;
043import java.util.LinkedHashMap;
044import java.util.List;
045import java.util.Map;
046import java.util.TreeMap;
047
048import com.unboundid.ldap.sdk.ResultCode;
049import com.unboundid.ldap.sdk.Version;
050import com.unboundid.util.ColumnFormatter;
051import com.unboundid.util.CommandLineTool;
052import com.unboundid.util.FormattableColumn;
053import com.unboundid.util.HorizontalAlignment;
054import com.unboundid.util.NotNull;
055import com.unboundid.util.Nullable;
056import com.unboundid.util.OutputFormat;
057import com.unboundid.util.StaticUtils;
058import com.unboundid.util.ThreadSafety;
059import com.unboundid.util.ThreadSafetyLevel;
060import com.unboundid.util.args.ArgumentException;
061import com.unboundid.util.args.ArgumentParser;
062import com.unboundid.util.args.BooleanArgument;
063import com.unboundid.util.args.IntegerArgument;
064import com.unboundid.util.args.StringArgument;
065import com.unboundid.util.json.JSONField;
066import com.unboundid.util.json.JSONObject;
067
068import static com.unboundid.ldap.sdk.unboundidds.tools.ToolMessages.*;
069
070
071
072/**
073 * This class provides a command-line tool that can be used to list LDAP result
074 * code names and their numeric values, or to search for result codes that match
075 * a given name or value.
076 * <BR>
077 * <BLOCKQUOTE>
078 *   <B>NOTE:</B>  This class, and other classes within the
079 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
080 *   supported for use against Ping Identity, UnboundID, and
081 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
082 *   for proprietary functionality or for external specifications that are not
083 *   considered stable or mature enough to be guaranteed to work in an
084 *   interoperable way with other types of LDAP servers.
085 * </BLOCKQUOTE>
086 */
087@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
088public final class LDAPResultCode
089       extends CommandLineTool
090{
091  /**
092   * The column at which to wrap long lines.
093   */
094  private static final int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 2;
095
096
097
098  /**
099   * The name of the JSON field that will hold the integer value for the result
100   * code.
101   */
102  @NotNull private static final String JSON_FIELD_INT_VALUE = "int-value";
103
104
105
106  /**
107   * The name of the JSON field that will hold the name for the result code.
108   */
109  @NotNull private static final String JSON_FIELD_NAME = "name";
110
111
112
113  /**
114   * The output format value that indicates that output should be generated as
115   * comma-separated values.
116   */
117  @NotNull private static final String OUTPUT_FORMAT_CSV = "csv";
118
119
120
121  /**
122   * The output format value that indicates that output should be generated as
123   * JSON objects.
124   */
125  @NotNull private static final String OUTPUT_FORMAT_JSON = "json";
126
127
128
129  /**
130   * The output format value that indicates that output should be generated as
131   * tab-delimited text.
132   */
133  @NotNull private static final String OUTPUT_FORMAT_TAB_DELIMITED =
134       "tab-delimited";
135
136
137
138  /**
139   * The output format value that indicates that output should be generated as
140   * a table.
141   */
142  @NotNull private static final String OUTPUT_FORMAT_TABLE = "table";
143
144
145
146  // The argument used to indicate that the tool should list result codes in
147  // alphabetic order rather than numeric order.
148  @Nullable private BooleanArgument alphabeticOrderArg;
149
150  // The argument used to indicate that the tool should list all defined result
151  // codes.
152  @Nullable private BooleanArgument listArg;
153
154  // The argument used to indicate that the tool should generate tab-delimited
155  // text output rather than as a table.
156  @Nullable private BooleanArgument scriptFriendlyArg;
157
158  // The argument used to indicate that the tool should search for the result
159  // code with the specified integer value.
160  @Nullable private IntegerArgument intValueArg;
161
162  // The argument used to specify the output format for the tool.
163  @Nullable private StringArgument outputFormatArg;
164
165  // The argument used to indicate that the tool should search for result codes
166  // that have the specified string in their name
167  @Nullable private StringArgument searchArg;
168
169
170
171  /**
172   * Runs this tool with the provided set of command-line arguments.
173   *
174   * @param  args  The command-line arguments provided to this program.  It may
175   *               be empty, but must not be {@code null}.
176   */
177  public static void main(@NotNull final String... args)
178  {
179    final ResultCode resultCode = main(System.out, System.err, args);
180    if (resultCode != ResultCode.SUCCESS)
181    {
182      System.exit(resultCode.intValue());
183    }
184  }
185
186
187
188  /**
189   * Runs this tool with the provided set of command-line arguments.
190   *
191   * @param  out   The output stream to use for standard output.  It may be
192   *               {@code null} if standard output should be suppressed.
193   * @param  err   The output stream to use for standard error.  It may be
194   *               {@code null} if standard error should be suppressed.
195   * @param  args  The command-line arguments provided to this program.  It may
196   *               be empty, but must not be {@code null}.
197   *
198   * @return  A result code that indicates the result of tool processing.
199   */
200  @NotNull()
201  public static ResultCode main(@Nullable final OutputStream out,
202                                @Nullable final OutputStream err,
203                                @NotNull final String... args)
204  {
205    final LDAPResultCode tool = new LDAPResultCode(out, err);
206    return tool.runTool(args);
207  }
208
209
210
211  /**
212   * Creates a new instance of this tool with the provided output and error
213   * streams.
214   *
215   * @param  out  The output stream to use for standard output.  It may be
216   *              {@code null} if standard output should be suppressed.
217   * @param  err  The output stream to use for standard error.  It may be
218   *              {@code null} if standard error should be suppressed.
219   */
220  public LDAPResultCode(@Nullable final OutputStream out,
221                        @Nullable final OutputStream err)
222  {
223    super(out, err);
224
225    alphabeticOrderArg = null;
226    listArg = null;
227    scriptFriendlyArg = null;
228    intValueArg = null;
229    outputFormatArg = null;
230    searchArg = null;
231  }
232
233
234
235  /**
236   * {@inheritDoc}
237   */
238  @Override()
239  @NotNull()
240  public String getToolName()
241  {
242    return "ldap-result-code";
243  }
244
245
246
247  /**
248   * {@inheritDoc}
249   */
250  @Override()
251  @NotNull()
252  public String getToolDescription()
253  {
254    return INFO_LDAP_RC_TOOL_DESC_1.get();
255  }
256
257
258
259  /**
260   * {@inheritDoc}
261   */
262  @Override()
263  @NotNull()
264  public List<String> getAdditionalDescriptionParagraphs()
265  {
266    return Arrays.asList(
267         INFO_LDAP_RC_TOOL_DESC_2.get(),
268         INFO_LDAP_RC_TOOL_DESC_3.get());
269  }
270
271
272
273  /**
274   * {@inheritDoc}
275   */
276  @Override()
277  @NotNull()
278  public String getToolVersion()
279  {
280    return Version.NUMERIC_VERSION_STRING;
281  }
282
283
284
285  /**
286   * {@inheritDoc}
287   */
288  @Override()
289  public boolean supportsInteractiveMode()
290  {
291    return true;
292  }
293
294
295
296  /**
297   * {@inheritDoc}
298   */
299  @Override()
300  public boolean defaultsToInteractiveMode()
301  {
302    return false;
303  }
304
305
306
307  /**
308   * {@inheritDoc}
309   */
310  @Override()
311  public boolean supportsPropertiesFile()
312  {
313    return true;
314  }
315
316
317
318  /**
319   * {@inheritDoc}
320   */
321  @Override()
322  protected boolean supportsOutputFile()
323  {
324    return true;
325  }
326
327
328
329  /**
330   * {@inheritDoc}
331   */
332  @Override()
333  protected boolean supportsDebugLogging()
334  {
335    return true;
336  }
337
338
339
340  /**
341   * {@inheritDoc}
342   */
343  @Override()
344  protected boolean logToolInvocationByDefault()
345  {
346    return false;
347  }
348
349
350
351  /**
352   * {@inheritDoc}
353   */
354  @Override()
355  public void addToolArguments(@NotNull final ArgumentParser parser)
356         throws ArgumentException
357  {
358    listArg = new BooleanArgument('l', "list", 1,
359         INFO_LDAP_RC_ARG_DESC_LIST.get());
360    parser.addArgument(listArg);
361
362    intValueArg = new IntegerArgument('i', "int-value", false, 1,
363         INFO_LDAP_RC_ARG_PLACEHOLDER_INT_VALUE.get(),
364         INFO_LDAP_RC_ARG_DESC_INT_VALUE.get());
365    intValueArg.addLongIdentifier("intValue", true);
366    parser.addArgument(intValueArg);
367
368    searchArg = new StringArgument('S', "search", false, 1,
369         INFO_LDAP_RC_ARG_PLACEHOLDER_SEARCH_STRING.get(),
370         INFO_LDAP_RC_ARG_DESC_SEARCH.get());
371    parser.addArgument(searchArg);
372
373    alphabeticOrderArg = new BooleanArgument('a', "alphabetic-order", 1,
374         INFO_LDAP_RC_ARG_DESC_ALPHABETIC.get());
375    alphabeticOrderArg.addLongIdentifier("alphabeticOrder", true);
376    alphabeticOrderArg.addLongIdentifier("alphabetical-order", true);
377    alphabeticOrderArg.addLongIdentifier("alphabeticalOrder", true);
378    alphabeticOrderArg.addLongIdentifier("alphabetic", true);
379    alphabeticOrderArg.addLongIdentifier("alphabetical", true);
380    parser.addArgument(alphabeticOrderArg);
381
382    outputFormatArg = new StringArgument(null, "output-format", false, 1,
383         INFO_LDAP_RC_ARG_PLACEHOLDER_OUTPUT_FORMAT.get(),
384         INFO_LDAP_RC_ARG_DESC_OUTPUT_FORMAT.get(),
385         StaticUtils.setOf(
386              OUTPUT_FORMAT_CSV,
387              OUTPUT_FORMAT_JSON,
388              OUTPUT_FORMAT_TAB_DELIMITED,
389              OUTPUT_FORMAT_TABLE));
390    outputFormatArg.addLongIdentifier("outputFormat", true);
391    outputFormatArg.addLongIdentifier("format", true);
392    parser.addArgument(outputFormatArg);
393
394    scriptFriendlyArg = new BooleanArgument(null, "script-friendly", 1,
395         INFO_LDAP_RC_ARG_DESC_SCRIPT_FRIENDLY.get());
396    scriptFriendlyArg.addLongIdentifier("scriptFriendly", true);
397    scriptFriendlyArg.setHidden(true);
398    parser.addArgument(scriptFriendlyArg);
399
400    parser.addExclusiveArgumentSet(listArg, intValueArg, searchArg);
401    parser.addExclusiveArgumentSet(outputFormatArg, scriptFriendlyArg);
402  }
403
404
405
406  /**
407   * {@inheritDoc}
408   */
409  @Override()
410  @NotNull()
411  public ResultCode doToolProcessing()
412  {
413    // Get all result codes that should be included in the output.
414    final Map<Integer,ResultCode> resultCodesByIntValue = new TreeMap<>();
415    final Map<String,ResultCode> resultCodesByName = new TreeMap<>();
416    if ((intValueArg != null) && intValueArg.isPresent())
417    {
418      final int intValue = intValueArg.getValue();
419      final ResultCode rc = ResultCode.valueOf(intValue, null, false);
420      if (rc != null)
421      {
422        resultCodesByIntValue.put(intValue, rc);
423        resultCodesByName.put(StaticUtils.toLowerCase(rc.getName()), rc);
424      }
425    }
426    else
427    {
428      final String searchString;
429      if ((searchArg != null) && searchArg.isPresent())
430      {
431        searchString = StaticUtils.toLowerCase(searchArg.getValue());
432      }
433      else
434      {
435        searchString = null;
436      }
437
438      for (final ResultCode rc : ResultCode.values())
439      {
440        final String name = rc.getName();
441        final String lowerName = StaticUtils.toLowerCase(name);
442        if (searchString != null)
443        {
444          if (! lowerName.contains(searchString))
445          {
446            continue;
447          }
448        }
449
450        resultCodesByIntValue.put(rc.intValue(), rc);
451        resultCodesByName.put(lowerName, rc);
452      }
453    }
454
455
456    // If there weren't any matching result codes, then inform the user and
457    // exit with an error.
458    if (resultCodesByIntValue.isEmpty())
459    {
460      wrapErr(0, WRAP_COLUMN, ERR_LDAP_RC_NO_RESULTS.get());
461      return ResultCode.NO_RESULTS_RETURNED;
462    }
463
464
465    // Iterate through the matching result codes and figure out how many
466    // characters are in the longest name and
467    final String nameLabel = INFO_LDAP_RC_NAME_LABEL.get();
468    final String intValueLabel = INFO_LDAP_RC_INT_VALUE_LABEL.get();
469    int numCharsInLongestName = nameLabel.length();
470    int numCharsInLongestIntValue = intValueLabel.length();
471    for (final Map.Entry<Integer,ResultCode> e :
472         resultCodesByIntValue.entrySet())
473    {
474      final String intValueString = String.valueOf(e.getKey());
475      numCharsInLongestIntValue =
476           Math.max(numCharsInLongestIntValue, intValueString.length());
477
478      final String name = e.getValue().getName();
479      numCharsInLongestName = Math.max(numCharsInLongestName, name.length());
480    }
481
482
483    // Construct the column formatter that will be used to generate the output.
484    final boolean json;
485    final OutputFormat outputFormat;
486    final boolean scriptFriendly =
487         ((scriptFriendlyArg != null) && scriptFriendlyArg.isPresent());
488    if (scriptFriendly)
489    {
490      json = false;
491      outputFormat = OutputFormat.TAB_DELIMITED_TEXT;
492    }
493    else if ((outputFormatArg != null) && outputFormatArg.isPresent())
494    {
495      final String outputFormatValue =
496           StaticUtils.toLowerCase(outputFormatArg.getValue());
497      if (outputFormatValue.equals(OUTPUT_FORMAT_CSV))
498      {
499        json = false;
500        outputFormat = OutputFormat.CSV;
501      }
502      else if (outputFormatValue.equals(OUTPUT_FORMAT_JSON))
503      {
504        json = true;
505        outputFormat = null;
506      }
507      else if (outputFormatValue.equals(OUTPUT_FORMAT_TAB_DELIMITED))
508      {
509        json = false;
510        outputFormat = OutputFormat.TAB_DELIMITED_TEXT;
511      }
512      else
513      {
514        json = false;
515        outputFormat = OutputFormat.COLUMNS;
516      }
517    }
518    else
519    {
520      json = false;
521      outputFormat = OutputFormat.COLUMNS;
522    }
523
524    final ColumnFormatter formatter;
525    if (json)
526    {
527      formatter = null;
528    }
529    else
530    {
531      formatter = new ColumnFormatter(false, null, outputFormat, " | ",
532           new FormattableColumn(numCharsInLongestName,
533                HorizontalAlignment.LEFT, nameLabel),
534           new FormattableColumn(numCharsInLongestIntValue,
535                HorizontalAlignment.LEFT, intValueLabel));
536    }
537
538
539    // Display the table header, if appropriate.
540    if ((formatter != null) && (outputFormat == OutputFormat.COLUMNS))
541    {
542      for (final String line : formatter.getHeaderLines(true))
543      {
544        out(line);
545      }
546    }
547
548
549    // Display the main output.
550    final Collection<ResultCode> resultCodes;
551    if ((alphabeticOrderArg != null) && alphabeticOrderArg.isPresent())
552    {
553      resultCodes = resultCodesByName.values();
554    }
555    else
556    {
557      resultCodes = resultCodesByIntValue.values();
558    }
559
560    for (final ResultCode rc : resultCodes)
561    {
562      if (formatter == null)
563      {
564        final JSONObject jsonObject = new JSONObject(
565             new JSONField(JSON_FIELD_NAME, rc.getName()),
566             new JSONField(JSON_FIELD_INT_VALUE, rc.intValue()));
567        out(jsonObject.toSingleLineString());
568      }
569      else
570      {
571        out(formatter.formatRow(rc.getName(), rc.intValue()));
572      }
573    }
574
575    return ResultCode.SUCCESS;
576  }
577
578
579
580  /**
581   * {@inheritDoc}
582   */
583  @Override()
584  @NotNull()
585  public LinkedHashMap<String[],String> getExampleUsages()
586  {
587    final LinkedHashMap<String[],String> examples = new LinkedHashMap<>();
588
589    examples.put(
590         StaticUtils.NO_STRINGS,
591         INFO_LDAP_RC_EXAMPLE_1.get());
592
593    examples.put(
594         new String[]
595         {
596           "--int-value", "49",
597           "--output-format", "json"
598         },
599         INFO_LDAP_RC_EXAMPLE_2.get());
600
601    examples.put(
602         new String[]
603         {
604           "--search", "attribute"
605         },
606         INFO_LDAP_RC_EXAMPLE_3.get());
607
608    return examples;
609  }
610}