001/*
002 * Copyright 2008-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2008-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) 2008-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.ArrayList;
041import java.util.Collections;
042import java.util.Iterator;
043import java.util.List;
044
045import com.unboundid.ldap.sdk.Filter;
046import com.unboundid.ldap.sdk.LDAPException;
047import com.unboundid.util.Debug;
048import com.unboundid.util.Mutable;
049import com.unboundid.util.NotNull;
050import com.unboundid.util.Nullable;
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 or more
060 * search filter values.  Filter arguments must take values, and those values
061 * must be able to be parsed as LDAP search filters.
062 */
063@Mutable()
064@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
065public final class FilterArgument
066       extends Argument
067{
068  /**
069   * The serial version UID for this serializable class.
070   */
071  private static final long serialVersionUID = -1889200072476038957L;
072
073
074
075  // The set of values assigned to this argument.
076  @NotNull private final ArrayList<Filter> values;
077
078  // The argument value validators that have been registered for this argument.
079  @NotNull private final List<ArgumentValueValidator> validators;
080
081  // The list of default values for this argument.
082  @Nullable private final List<Filter> defaultValues;
083
084
085
086  /**
087   * Creates a new filter argument with the provided information.  It will not
088   * be required, will permit at most one occurrence, will use a default
089   * placeholder, and will not have a default value.
090   *
091   * @param  shortIdentifier   The short identifier for this argument.  It may
092   *                           not be {@code null} if the long identifier is
093   *                           {@code null}.
094   * @param  longIdentifier    The long identifier for this argument.  It may
095   *                           not be {@code null} if the short identifier is
096   *                           {@code null}.
097   * @param  description       A human-readable description for this argument.
098   *                           It must not be {@code null}.
099   *
100   * @throws  ArgumentException  If there is a problem with the definition of
101   *                             this argument.
102   */
103  public FilterArgument(@Nullable final Character shortIdentifier,
104                        @Nullable final String longIdentifier,
105                        @NotNull final String description)
106         throws ArgumentException
107  {
108    this(shortIdentifier, longIdentifier, false, 1, null, description);
109  }
110
111
112
113  /**
114   * Creates a new filter argument with the provided information.  It will not
115   * have a default value.
116   *
117   * @param  shortIdentifier   The short identifier for this argument.  It may
118   *                           not be {@code null} if the long identifier is
119   *                           {@code null}.
120   * @param  longIdentifier    The long identifier for this argument.  It may
121   *                           not be {@code null} if the short identifier is
122   *                           {@code null}.
123   * @param  isRequired        Indicates whether this argument is required to
124   *                           be provided.
125   * @param  maxOccurrences    The maximum number of times this argument may be
126   *                           provided on the command line.  A value less than
127   *                           or equal to zero indicates that it may be present
128   *                           any number of times.
129   * @param  valuePlaceholder  A placeholder to display in usage information to
130   *                           indicate that a value must be provided.  It may
131   *                           be {@code null} if a default placeholder should
132   *                           be used.
133   * @param  description       A human-readable description for this argument.
134   *                           It must not be {@code null}.
135   *
136   * @throws  ArgumentException  If there is a problem with the definition of
137   *                             this argument.
138   */
139  public FilterArgument(@Nullable final Character shortIdentifier,
140                        @Nullable final String longIdentifier,
141                        final boolean isRequired, final int maxOccurrences,
142                        @Nullable final String valuePlaceholder,
143                        @NotNull final String description)
144         throws ArgumentException
145  {
146    this(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
147         valuePlaceholder, description, (List<Filter>) null);
148  }
149
150
151
152  /**
153   * Creates a new filter argument with the provided information.
154   *
155   * @param  shortIdentifier   The short identifier for this argument.  It may
156   *                           not be {@code null} if the long identifier is
157   *                           {@code null}.
158   * @param  longIdentifier    The long identifier for this argument.  It may
159   *                           not be {@code null} if the short identifier is
160   *                           {@code null}.
161   * @param  isRequired        Indicates whether this argument is required to
162   *                           be provided.
163   * @param  maxOccurrences    The maximum number of times this argument may be
164   *                           provided on the command line.  A value less than
165   *                           or equal to zero indicates that it may be present
166   *                           any number of times.
167   * @param  valuePlaceholder  A placeholder to display in usage information to
168   *                           indicate that a value must be provided.  It may
169   *                           be {@code null} if a default placeholder should
170   *                           be used.
171   * @param  description       A human-readable description for this argument.
172   *                           It must not be {@code null}.
173   * @param  defaultValue      The default value to use for this argument if no
174   *                           values were provided.  It may be {@code null} if
175   *                           there should be no default values.
176   *
177   * @throws  ArgumentException  If there is a problem with the definition of
178   *                             this argument.
179   */
180  public FilterArgument(@Nullable final Character shortIdentifier,
181                        @Nullable final String longIdentifier,
182                        final boolean isRequired, final int maxOccurrences,
183                        @Nullable final String valuePlaceholder,
184                        @NotNull final String description,
185                        @Nullable final Filter defaultValue)
186         throws ArgumentException
187  {
188    this(shortIdentifier, longIdentifier, isRequired, maxOccurrences,
189         valuePlaceholder, description,
190         ((defaultValue == null)
191              ? null
192              : Collections.singletonList(defaultValue)));
193  }
194
195
196
197  /**
198   * Creates a new filter argument with the provided information.
199   *
200   * @param  shortIdentifier   The short identifier for this argument.  It may
201   *                           not be {@code null} if the long identifier is
202   *                           {@code null}.
203   * @param  longIdentifier    The long identifier for this argument.  It may
204   *                           not be {@code null} if the short identifier is
205   *                           {@code null}.
206   * @param  isRequired        Indicates whether this argument is required to
207   *                           be provided.
208   * @param  maxOccurrences    The maximum number of times this argument may be
209   *                           provided on the command line.  A value less than
210   *                           or equal to zero indicates that it may be present
211   *                           any number of times.
212   * @param  valuePlaceholder  A placeholder to display in usage information to
213   *                           indicate that a value must be provided.  It may
214   *                           be {@code null} if a default placeholder should
215   *                           be used.
216   * @param  description       A human-readable description for this argument.
217   *                           It must not be {@code null}.
218   * @param  defaultValues     The set of default values to use for this
219   *                           argument if no values were provided.
220   *
221   * @throws  ArgumentException  If there is a problem with the definition of
222   *                             this argument.
223   */
224  public FilterArgument(@Nullable final Character shortIdentifier,
225                        @Nullable final String longIdentifier,
226                        final boolean isRequired, final int maxOccurrences,
227                        @Nullable final String valuePlaceholder,
228                        @NotNull final String description,
229                        @Nullable final List<Filter> defaultValues)
230         throws ArgumentException
231  {
232    super(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
233         (valuePlaceholder == null)
234              ? INFO_PLACEHOLDER_FILTER.get()
235              : valuePlaceholder,
236         description);
237
238    if ((defaultValues == null) || defaultValues.isEmpty())
239    {
240      this.defaultValues = null;
241    }
242    else
243    {
244      this.defaultValues = Collections.unmodifiableList(defaultValues);
245    }
246
247    values = new ArrayList<>(5);
248    validators = new ArrayList<>(5);
249  }
250
251
252
253  /**
254   * Creates a new filter argument that is a "clean" copy of the provided source
255   * argument.
256   *
257   * @param  source  The source argument to use for this argument.
258   */
259  private FilterArgument(@NotNull final FilterArgument source)
260  {
261    super(source);
262
263    defaultValues = source.defaultValues;
264    validators    = new ArrayList<>(source.validators);
265    values        = new ArrayList<>(5);
266  }
267
268
269
270  /**
271   * Retrieves the list of default values for this argument, which will be used
272   * if no values were provided.
273   *
274   * @return   The list of default values for this argument, or {@code null} if
275   *           there are no default values.
276   */
277  @Nullable()
278  public List<Filter> getDefaultValues()
279  {
280    return defaultValues;
281  }
282
283
284
285  /**
286   * Updates this argument to ensure that the provided validator will be invoked
287   * for any values provided to this argument.  This validator will be invoked
288   * after all other validation has been performed for this argument.
289   *
290   * @param  validator  The argument value validator to be invoked.  It must not
291   *                    be {@code null}.
292   */
293  public void addValueValidator(@NotNull final ArgumentValueValidator validator)
294  {
295    validators.add(validator);
296  }
297
298
299
300  /**
301   * {@inheritDoc}
302   */
303  @Override()
304  protected void addValue(@NotNull final String valueString)
305            throws ArgumentException
306  {
307    final Filter filter;
308    try
309    {
310      filter = Filter.create(valueString);
311    }
312    catch (final LDAPException le)
313    {
314      Debug.debugException(le);
315      throw new ArgumentException(ERR_FILTER_VALUE_NOT_FILTER.get(valueString,
316                                       getIdentifierString(), le.getMessage()),
317                                  le);
318    }
319
320    if (values.size() >= getMaxOccurrences())
321    {
322      throw new ArgumentException(ERR_ARG_MAX_OCCURRENCES_EXCEEDED.get(
323                                       getIdentifierString()));
324    }
325
326    for (final ArgumentValueValidator v : validators)
327    {
328      v.validateArgumentValue(this, valueString);
329    }
330
331    values.add(filter);
332  }
333
334
335
336  /**
337   * Retrieves the value for this argument, or the default value if none was
338   * provided.  If there are multiple values, then the first will be returned.
339   *
340   * @return  The value for this argument, or the default value if none was
341   *          provided, or {@code null} if there is no value and no default
342   *          value.
343   */
344  @Nullable()
345  public Filter getValue()
346  {
347    if (values.isEmpty())
348    {
349      if ((defaultValues == null) || defaultValues.isEmpty())
350      {
351        return null;
352      }
353      else
354      {
355        return defaultValues.get(0);
356      }
357    }
358    else
359    {
360      return values.get(0);
361    }
362  }
363
364
365
366  /**
367   * Retrieves the set of values for this argument, or the default values if
368   * none were provided.
369   *
370   * @return  The set of values for this argument, or the default values if none
371   *          were provided.
372   */
373  @NotNull()
374  public List<Filter> getValues()
375  {
376    if (values.isEmpty() && (defaultValues != null))
377    {
378      return defaultValues;
379    }
380
381    return Collections.unmodifiableList(values);
382  }
383
384
385
386  /**
387   * {@inheritDoc}
388   */
389  @Override()
390  @NotNull()
391  public List<String> getValueStringRepresentations(final boolean useDefault)
392  {
393    final List<Filter> filters;
394    if (values.isEmpty())
395    {
396      if (useDefault)
397      {
398        filters = defaultValues;
399      }
400      else
401      {
402        return Collections.emptyList();
403      }
404    }
405    else
406    {
407      filters = values;
408    }
409
410    if ((filters == null) || filters.isEmpty())
411    {
412      return Collections.emptyList();
413    }
414
415    final ArrayList<String> valueStrings = new ArrayList<>(filters.size());
416    for (final Filter f : filters)
417    {
418      valueStrings.add(f.toString());
419    }
420    return Collections.unmodifiableList(valueStrings);
421  }
422
423
424
425  /**
426   * {@inheritDoc}
427   */
428  @Override()
429  protected boolean hasDefaultValue()
430  {
431    return ((defaultValues != null) && (! defaultValues.isEmpty()));
432  }
433
434
435
436  /**
437   * {@inheritDoc}
438   */
439  @Override()
440  @NotNull()
441  public String getDataTypeName()
442  {
443    return INFO_FILTER_TYPE_NAME.get();
444  }
445
446
447
448  /**
449   * {@inheritDoc}
450   */
451  @Override()
452  @NotNull()
453  public String getValueConstraints()
454  {
455    return INFO_FILTER_CONSTRAINTS.get();
456  }
457
458
459
460  /**
461   * {@inheritDoc}
462   */
463  @Override()
464  protected void reset()
465  {
466    super.reset();
467    values.clear();
468  }
469
470
471
472  /**
473   * {@inheritDoc}
474   */
475  @Override()
476  @NotNull()
477  public FilterArgument getCleanCopy()
478  {
479    return new FilterArgument(this);
480  }
481
482
483
484  /**
485   * {@inheritDoc}
486   */
487  @Override()
488  protected void addToCommandLine(@NotNull final List<String> argStrings)
489  {
490    for (final Filter f : values)
491    {
492      argStrings.add(getIdentifierString());
493      if (isSensitive())
494      {
495        argStrings.add("***REDACTED***");
496      }
497      else
498      {
499        argStrings.add(f.toString());
500      }
501    }
502  }
503
504
505
506  /**
507   * {@inheritDoc}
508   */
509  @Override()
510  public void toString(@NotNull final StringBuilder buffer)
511  {
512    buffer.append("FilterArgument(");
513    appendBasicToStringInfo(buffer);
514
515    if ((defaultValues != null) && (! defaultValues.isEmpty()))
516    {
517      if (defaultValues.size() == 1)
518      {
519        buffer.append(", defaultValue='");
520        buffer.append(defaultValues.get(0).toString());
521      }
522      else
523      {
524        buffer.append(", defaultValues={");
525
526        final Iterator<Filter> iterator = defaultValues.iterator();
527        while (iterator.hasNext())
528        {
529          buffer.append('\'');
530          buffer.append(iterator.next().toString());
531          buffer.append('\'');
532
533          if (iterator.hasNext())
534          {
535            buffer.append(", ");
536          }
537        }
538
539        buffer.append('}');
540      }
541    }
542
543    buffer.append(')');
544  }
545}