001/*
002 * Copyright 2010-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2010-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) 2010-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.util.args;
037
038
039
040import java.util.Collections;
041import java.util.HashMap;
042import java.util.List;
043import java.util.Map;
044import java.util.concurrent.atomic.AtomicReference;
045
046import com.unboundid.ldap.sdk.SearchScope;
047import com.unboundid.util.Mutable;
048import com.unboundid.util.NotNull;
049import com.unboundid.util.Nullable;
050import com.unboundid.util.StaticUtils;
051import com.unboundid.util.ThreadSafety;
052import com.unboundid.util.ThreadSafetyLevel;
053
054import static com.unboundid.util.args.ArgsMessages.*;
055
056
057
058/**
059 * This class defines an argument that is intended to hold one search scope
060 * values.  Scope arguments must take values, and those arguments must represent
061 * valid search scopes.  Supported scope values include:
062 * <UL>
063 *   <LI>baseObject scope -- base, baseObject, base-object, 0</LI>
064 *   <LI>singleLevel scope -- one, singleLevel, single-level, oneLevel,
065 *       one-level, 1</LI>
066 *   <LI>wholeSubtree scope -- sub, subtree, wholeSubtree, whole-subtree, 2</LI>
067 *   <LI>subordinateSubtree scope -- subord, subordinate, subordinates,
068 *       subordinateSubtree, subordinate-subtree, 3</LI>
069 * </UL>
070 */
071@Mutable()
072@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
073public final class ScopeArgument
074       extends Argument
075{
076  /**
077   * A map of value strings to the corresponding search scopes.
078   */
079  @NotNull private static final Map<String,SearchScope> SCOPE_STRINGS;
080
081  static
082  {
083    final HashMap<String,SearchScope> scopeMap =
084         new HashMap<>(StaticUtils.computeMapCapacity(21));
085
086    scopeMap.put("base", SearchScope.BASE);
087    scopeMap.put("baseobject", SearchScope.BASE);
088    scopeMap.put("base-object", SearchScope.BASE);
089    scopeMap.put("0", SearchScope.BASE);
090
091    scopeMap.put("one", SearchScope.ONE);
092    scopeMap.put("singlelevel", SearchScope.ONE);
093    scopeMap.put("single-level", SearchScope.ONE);
094    scopeMap.put("onelevel", SearchScope.ONE);
095    scopeMap.put("one-level", SearchScope.ONE);
096    scopeMap.put("1", SearchScope.ONE);
097
098    scopeMap.put("sub", SearchScope.SUB);
099    scopeMap.put("subtree", SearchScope.SUB);
100    scopeMap.put("wholesubtree", SearchScope.SUB);
101    scopeMap.put("whole-subtree", SearchScope.SUB);
102    scopeMap.put("2", SearchScope.SUB);
103
104    scopeMap.put("subord", SearchScope.SUBORDINATE_SUBTREE);
105    scopeMap.put("subordinate", SearchScope.SUBORDINATE_SUBTREE);
106    scopeMap.put("subordinates", SearchScope.SUBORDINATE_SUBTREE);
107    scopeMap.put("subordinatesubtree", SearchScope.SUBORDINATE_SUBTREE);
108    scopeMap.put("subordinate-subtree", SearchScope.SUBORDINATE_SUBTREE);
109    scopeMap.put("3", SearchScope.SUBORDINATE_SUBTREE);
110
111    SCOPE_STRINGS = Collections.unmodifiableMap(scopeMap);
112  }
113
114
115
116  /**
117   * The serial version UID for this serializable class.
118   */
119  private static final long serialVersionUID = 5962857448814911423L;
120
121
122
123  // The value assigned to this argument.
124  @NotNull private final AtomicReference<SearchScope> value;
125
126  // The default value for this argument.
127  @Nullable private final SearchScope defaultValue;
128
129
130
131  /**
132   * Creates a new search scope argument with the provided information.  It will
133   * not be required, will use a default placeholder, and will not have a
134   * default value.
135   *
136   * @param  shortIdentifier   The short identifier for this argument.  It may
137   *                           not be {@code null} if the long identifier is
138   *                           {@code null}.
139   * @param  longIdentifier    The long identifier for this argument.  It may
140   *                           not be {@code null} if the short identifier is
141   *                           {@code null}.
142   * @param  description       A human-readable description for this argument.
143   *                           It must not be {@code null}.
144   *
145   * @throws  ArgumentException  If there is a problem with the definition of
146   *                             this argument.
147   */
148  public ScopeArgument(@Nullable final Character shortIdentifier,
149                       @Nullable final String longIdentifier,
150                       @NotNull final String description)
151         throws ArgumentException
152  {
153    this(shortIdentifier, longIdentifier, false, null, description);
154  }
155
156
157
158  /**
159   * Creates a new search scope argument with the provided information.  It will
160   * not have a default value.
161   *
162   * @param  shortIdentifier   The short identifier for this argument.  It may
163   *                           not be {@code null} if the long identifier is
164   *                           {@code null}.
165   * @param  longIdentifier    The long identifier for this argument.  It may
166   *                           not be {@code null} if the short identifier is
167   *                           {@code null}.
168   * @param  isRequired        Indicates whether this argument is required to
169   *                           be provided.
170   * @param  valuePlaceholder  A placeholder to display in usage information to
171   *                           indicate that a value must be provided.  It may
172   *                           be {@code null} if a default placeholder should
173   *                           be used.
174   * @param  description       A human-readable description for this argument.
175   *                           It must not be {@code null}.
176   *
177   * @throws  ArgumentException  If there is a problem with the definition of
178   *                             this argument.
179   */
180  public ScopeArgument(@Nullable final Character shortIdentifier,
181                       @Nullable final String longIdentifier,
182                       final boolean isRequired,
183                       @Nullable final String valuePlaceholder,
184                       @NotNull final String description)
185         throws ArgumentException
186  {
187    this(shortIdentifier, longIdentifier, isRequired,  valuePlaceholder,
188         description, null);
189  }
190
191
192
193  /**
194   * Creates a new search scope argument with the provided information.
195   *
196   * @param  shortIdentifier   The short identifier for this argument.  It may
197   *                           not be {@code null} if the long identifier is
198   *                           {@code null}.
199   * @param  longIdentifier    The long identifier for this argument.  It may
200   *                           not be {@code null} if the short identifier is
201   *                           {@code null}.
202   * @param  isRequired        Indicates whether this argument is required to
203   *                           be provided.
204   * @param  valuePlaceholder  A placeholder to display in usage information to
205   *                           indicate that a value must be provided.  It may
206   *                           be {@code null} if a default placeholder should
207   *                           be used.
208   * @param  description       A human-readable description for this argument.
209   *                           It must not be {@code null}.
210   * @param  defaultValue      The default value to use for this argument if no
211   *                           values were provided.  It may be {@code null} if
212   *                           there should be no default values.
213   *
214   * @throws  ArgumentException  If there is a problem with the definition of
215   *                             this argument.
216   */
217  public ScopeArgument(@Nullable final Character shortIdentifier,
218                       @Nullable final String longIdentifier,
219                       final boolean isRequired,
220                       @Nullable final String valuePlaceholder,
221                       @NotNull final String description,
222                       @Nullable final SearchScope defaultValue)
223         throws ArgumentException
224  {
225    super(shortIdentifier, longIdentifier, isRequired,  1,
226         (valuePlaceholder == null)
227              ? INFO_PLACEHOLDER_SCOPE.get()
228              : valuePlaceholder,
229         description);
230
231    this.defaultValue = defaultValue;
232
233    value = new AtomicReference<>();
234  }
235
236
237
238  /**
239   * Creates a new scope argument that is a "clean" copy of the provided
240   * source argument.
241   *
242   * @param  source  The source argument to use for this argument.
243   */
244  private ScopeArgument(@NotNull final ScopeArgument source)
245  {
246    super(source);
247
248    defaultValue = source.defaultValue;
249    value        = new AtomicReference<>();
250  }
251
252
253
254  /**
255   * Retrieves the default value for this argument, which will be used if no
256   * value was provided.
257   *
258   * @return  The default value for this argument, or {@code null} if there is
259   *          no default value.
260   */
261  @Nullable()
262  public SearchScope getDefaultValue()
263  {
264    return defaultValue;
265  }
266
267
268
269  /**
270   * {@inheritDoc}
271   */
272  @Override()
273  protected void addValue(@NotNull final String valueString)
274            throws ArgumentException
275  {
276    final SearchScope scope =
277         SCOPE_STRINGS.get(StaticUtils.toLowerCase(valueString));
278    if (scope == null)
279    {
280      throw new ArgumentException(ERR_SCOPE_VALUE_NOT_VALID.get(valueString,
281           getIdentifierString()));
282    }
283
284    if (! value.compareAndSet(null, scope))
285    {
286      throw new ArgumentException(ERR_ARG_MAX_OCCURRENCES_EXCEEDED.get(
287                                       getIdentifierString()));
288    }
289  }
290
291
292
293  /**
294   * Retrieves the value for this argument, or the default value if none was
295   * provided.
296   *
297   * @return  The value for this argument, or the default value if none was
298   *          provided, or {@code null} if there is no value and no default
299   *          value.
300   */
301  @Nullable()
302  public SearchScope getValue()
303  {
304    final SearchScope s = value.get();
305    if (s == null)
306    {
307      return defaultValue;
308    }
309    else
310    {
311      return s;
312    }
313  }
314
315
316
317  /**
318   * {@inheritDoc}
319   */
320  @Override()
321  @NotNull()
322  public List<String> getValueStringRepresentations(final boolean useDefault)
323  {
324    SearchScope s = value.get();
325    if (useDefault && (s == null))
326    {
327      s = defaultValue;
328    }
329
330    if (s == null)
331    {
332      return Collections.emptyList();
333    }
334
335    final String scopeStr;
336    switch (s.intValue())
337    {
338      case SearchScope.BASE_INT_VALUE:
339        scopeStr = "base";
340        break;
341      case SearchScope.ONE_INT_VALUE:
342        scopeStr = "one";
343        break;
344      case SearchScope.SUB_INT_VALUE:
345        scopeStr = "sub";
346        break;
347      case SearchScope.SUBORDINATE_SUBTREE_INT_VALUE:
348        scopeStr = "subordinates";
349        break;
350      default:
351        scopeStr = s.getName();
352        break;
353    }
354
355    return Collections.singletonList(scopeStr);
356  }
357
358
359
360  /**
361   * {@inheritDoc}
362   */
363  @Override()
364  protected boolean hasDefaultValue()
365  {
366    return (defaultValue != null);
367  }
368
369
370
371  /**
372   * {@inheritDoc}
373   */
374  @Override()
375  @NotNull()
376  public String getDataTypeName()
377  {
378    return INFO_SCOPE_TYPE_NAME.get();
379  }
380
381
382
383  /**
384   * {@inheritDoc}
385   */
386  @Override()
387  @NotNull()
388  public String getValueConstraints()
389  {
390    return INFO_SCOPE_CONSTRAINTS.get();
391  }
392
393
394
395  /**
396   * {@inheritDoc}
397   */
398  @Override()
399  protected void reset()
400  {
401    super.reset();
402    value.set(null);
403  }
404
405
406
407  /**
408   * {@inheritDoc}
409   */
410  @Override()
411  @NotNull()
412  public ScopeArgument getCleanCopy()
413  {
414    return new ScopeArgument(this);
415  }
416
417
418
419  /**
420   * {@inheritDoc}
421   */
422  @Override()
423  protected void addToCommandLine(@NotNull final List<String> argStrings)
424  {
425    final SearchScope s = value.get();
426    if (s != null)
427    {
428      if (isSensitive())
429      {
430        argStrings.add(getIdentifierString());
431        argStrings.add("***REDACTED***");
432        return;
433      }
434
435      switch (s.intValue())
436      {
437        case SearchScope.BASE_INT_VALUE:
438          argStrings.add(getIdentifierString());
439          argStrings.add("base");
440          break;
441        case SearchScope.ONE_INT_VALUE:
442          argStrings.add(getIdentifierString());
443          argStrings.add("one");
444          break;
445        case SearchScope.SUB_INT_VALUE:
446          argStrings.add(getIdentifierString());
447          argStrings.add("sub");
448          break;
449        case SearchScope.SUBORDINATE_SUBTREE_INT_VALUE:
450          argStrings.add(getIdentifierString());
451          argStrings.add("subordinates");
452          break;
453      }
454    }
455  }
456
457
458
459  /**
460   * {@inheritDoc}
461   */
462  @Override()
463  public void toString(@NotNull final StringBuilder buffer)
464  {
465    buffer.append("ScopeArgument(");
466    appendBasicToStringInfo(buffer);
467
468    if (defaultValue != null)
469    {
470      buffer.append(", defaultValue='");
471      switch (defaultValue.intValue())
472      {
473        case SearchScope.BASE_INT_VALUE:
474          buffer.append("base");
475          break;
476        case SearchScope.ONE_INT_VALUE:
477          buffer.append("one");
478          break;
479        case SearchScope.SUB_INT_VALUE:
480          buffer.append("sub");
481          break;
482        case SearchScope.SUBORDINATE_SUBTREE_INT_VALUE:
483          buffer.append("subordinate");
484          break;
485        default:
486          buffer.append(defaultValue.intValue());
487          break;
488      }
489      buffer.append('\'');
490    }
491
492    buffer.append(')');
493  }
494}