001/*
002 * Copyright 2016-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2016-2020 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) 2016-2020 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.text.ParseException;
041import java.text.SimpleDateFormat;
042import java.util.ArrayList;
043import java.util.Date;
044import java.util.Collections;
045import java.util.Iterator;
046import java.util.List;
047
048import com.unboundid.util.Debug;
049import com.unboundid.util.Mutable;
050import com.unboundid.util.ObjectPair;
051import com.unboundid.util.StaticUtils;
052import com.unboundid.util.ThreadSafety;
053import com.unboundid.util.ThreadSafetyLevel;
054
055import static com.unboundid.util.args.ArgsMessages.*;
056
057
058
059/**
060 * This class defines an argument that is intended to hold one or more
061 * timestamp values.  Values may be provided in any of the following formats:
062 * <UL>
063 *   <LI>Any valid generalized time format.</LI>
064 *   <LI>A local time zone timestamp in the format YYYYMMDDhhmmss.uuu</LI>
065 *   <LI>A local time zone timestamp in the format YYYYMMDDhhmmss</LI>
066 *   <LI>A local time zone timestamp in the format YYYYMMDDhhmm</LI>
067 * </UL>
068 */
069@Mutable()
070@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
071public final class TimestampArgument
072       extends Argument
073{
074  /**
075   * The serial version UID for this serializable class.
076   */
077  private static final long serialVersionUID = -4842934851103696096L;
078
079
080
081  // The argument value validators that have been registered for this argument.
082  private final List<ArgumentValueValidator> validators;
083
084  // The list of default values for this argument.
085  private final List<Date> defaultValues;
086
087  // The set of values assigned to this argument.
088  private final List<ObjectPair<Date,String>> values;
089
090
091
092  /**
093   * Creates a new timestamp argument with the provided information.  It will
094   * not be required, will permit at most one occurrence, will use a default
095   * placeholder, and will not have a default value.
096   *
097   * @param  shortIdentifier   The short identifier for this argument.  It may
098   *                           not be {@code null} if the long identifier is
099   *                           {@code null}.
100   * @param  longIdentifier    The long identifier for this argument.  It may
101   *                           not be {@code null} if the short identifier is
102   *                           {@code null}.
103   * @param  description       A human-readable description for this argument.
104   *                           It must not be {@code null}.
105   *
106   * @throws  ArgumentException  If there is a problem with the definition of
107   *                             this argument.
108   */
109  public TimestampArgument(final Character shortIdentifier,
110                           final String longIdentifier,
111                           final String description)
112         throws ArgumentException
113  {
114    this(shortIdentifier, longIdentifier, false, 1, null, description);
115  }
116
117
118
119  /**
120   * Creates a new timestamp argument with the provided information.  It will
121   * not have a default value.
122   *
123   * @param  shortIdentifier   The short identifier for this argument.  It may
124   *                           not be {@code null} if the long identifier is
125   *                           {@code null}.
126   * @param  longIdentifier    The long identifier for this argument.  It may
127   *                           not be {@code null} if the short identifier is
128   *                           {@code null}.
129   * @param  isRequired        Indicates whether this argument is required to
130   *                           be provided.
131   * @param  maxOccurrences    The maximum number of times this argument may be
132   *                           provided on the command line.  A value less than
133   *                           or equal to zero indicates that it may be present
134   *                           any number of times.
135   * @param  valuePlaceholder  A placeholder to display in usage information to
136   *                           indicate that a value must be provided.  It may
137   *                           be {@code null} if a default placeholder should
138   *                           be used.
139   * @param  description       A human-readable description for this argument.
140   *                           It must not be {@code null}.
141   *
142   * @throws  ArgumentException  If there is a problem with the definition of
143   *                             this argument.
144   */
145  public TimestampArgument(final Character shortIdentifier,
146                           final String longIdentifier,
147                           final boolean isRequired, final int maxOccurrences,
148                           final String valuePlaceholder,
149                           final String description)
150         throws ArgumentException
151  {
152    this(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
153         valuePlaceholder, description, (List<Date>) null);
154  }
155
156
157
158  /**
159   * Creates a new timestamp argument with the provided information.
160   *
161   * @param  shortIdentifier   The short identifier for this argument.  It may
162   *                           not be {@code null} if the long identifier is
163   *                           {@code null}.
164   * @param  longIdentifier    The long identifier for this argument.  It may
165   *                           not be {@code null} if the short identifier is
166   *                           {@code null}.
167   * @param  isRequired        Indicates whether this argument is required to
168   *                           be provided.
169   * @param  maxOccurrences    The maximum number of times this argument may be
170   *                           provided on the command line.  A value less than
171   *                           or equal to zero indicates that it may be present
172   *                           any number of times.
173   * @param  valuePlaceholder  A placeholder to display in usage information to
174   *                           indicate that a value must be provided.  It may
175   *                           be {@code null} if a default placeholder should
176   *                           be used.
177   * @param  description       A human-readable description for this argument.
178   *                           It must not be {@code null}.
179   * @param  defaultValue      The default value to use for this argument if no
180   *                           values were provided.
181   *
182   * @throws  ArgumentException  If there is a problem with the definition of
183   *                             this argument.
184   */
185  public TimestampArgument(final Character shortIdentifier,
186                           final String longIdentifier,
187                           final boolean isRequired, final int maxOccurrences,
188                           final String valuePlaceholder,
189                           final String description, final Date defaultValue)
190         throws ArgumentException
191  {
192    this(shortIdentifier, longIdentifier, isRequired, maxOccurrences,
193         valuePlaceholder, description,
194         ((defaultValue == null)
195              ? null
196              : Collections.singletonList(defaultValue)));
197  }
198
199
200
201  /**
202   * Creates a new timestamp argument with the provided information.
203   *
204   * @param  shortIdentifier   The short identifier for this argument.  It may
205   *                           not be {@code null} if the long identifier is
206   *                           {@code null}.
207   * @param  longIdentifier    The long identifier for this argument.  It may
208   *                           not be {@code null} if the short identifier is
209   *                           {@code null}.
210   * @param  isRequired        Indicates whether this argument is required to
211   *                           be provided.
212   * @param  maxOccurrences    The maximum number of times this argument may be
213   *                           provided on the command line.  A value less than
214   *                           or equal to zero indicates that it may be present
215   *                           any number of times.
216   * @param  valuePlaceholder  A placeholder to display in usage information to
217   *                           indicate that a value must be provided.  It may
218   *                           be {@code null} if a default placeholder should
219   *                           be used.
220   * @param  description       A human-readable description for this argument.
221   *                           It must not be {@code null}.
222   * @param  defaultValues     The set of default values to use for this
223   *                           argument if no values were provided.
224   *
225   * @throws  ArgumentException  If there is a problem with the definition of
226   *                             this argument.
227   */
228  public TimestampArgument(final Character shortIdentifier,
229                           final String longIdentifier,
230                           final boolean isRequired, final int maxOccurrences,
231                           final String valuePlaceholder,
232                           final String description,
233                           final List<Date> defaultValues)
234         throws ArgumentException
235  {
236    super(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
237         (valuePlaceholder == null)
238              ? INFO_PLACEHOLDER_TIMESTAMP.get()
239              : valuePlaceholder,
240         description);
241
242    if ((defaultValues == null) || defaultValues.isEmpty())
243    {
244      this.defaultValues = null;
245    }
246    else
247    {
248      this.defaultValues = Collections.unmodifiableList(defaultValues);
249    }
250
251    values = new ArrayList<>(5);
252    validators = new ArrayList<>(5);
253  }
254
255
256
257  /**
258   * Creates a new timestamp argument that is a "clean" copy of the provided
259   * source argument.
260   *
261   * @param  source  The source argument to use for this argument.
262   */
263  private TimestampArgument(final TimestampArgument source)
264  {
265    super(source);
266
267    defaultValues = source.defaultValues;
268    values        = new ArrayList<>(5);
269    validators    = new ArrayList<>(source.validators);
270  }
271
272
273
274  /**
275   * Retrieves the list of default values for this argument, which will be used
276   * if no values were provided.
277   *
278   * @return   The list of default values for this argument, or {@code null} if
279   *           there are no default values.
280   */
281  public List<Date> getDefaultValues()
282  {
283    return defaultValues;
284  }
285
286
287
288  /**
289   * Updates this argument to ensure that the provided validator will be invoked
290   * for any values provided to this argument.  This validator will be invoked
291   * after all other validation has been performed for this argument.
292   *
293   * @param  validator  The argument value validator to be invoked.  It must not
294   *                    be {@code null}.
295   */
296  public void addValueValidator(final ArgumentValueValidator validator)
297  {
298    validators.add(validator);
299  }
300
301
302
303  /**
304   * {@inheritDoc}
305   */
306  @Override()
307  protected void addValue(final String valueString)
308            throws ArgumentException
309  {
310    final Date d;
311    try
312    {
313      d = parseTimestamp(valueString);
314    }
315    catch (final Exception e)
316    {
317      Debug.debugException(e);
318      throw new ArgumentException(
319           ERR_TIMESTAMP_VALUE_NOT_TIMESTAMP.get(valueString,
320                getIdentifierString()),
321           e);
322    }
323
324
325    if (values.size() >= getMaxOccurrences())
326    {
327      throw new ArgumentException(ERR_ARG_MAX_OCCURRENCES_EXCEEDED.get(
328                                       getIdentifierString()));
329    }
330
331    for (final ArgumentValueValidator v : validators)
332    {
333      v.validateArgumentValue(this, valueString);
334    }
335
336    values.add(new ObjectPair<>(d, valueString));
337  }
338
339
340
341  /**
342   * Parses the provided string as a timestamp using one of the supported
343   * formats.
344   *
345   * @param  s  The string to parse as a timestamp.  It must not be
346   *            {@code null}.
347   *
348   * @return  The {@code Date} object parsed from the provided timestamp.
349   *
350   * @throws  ParseException  If the provided string cannot be parsed as a
351   *                          timestamp.
352   */
353  public static Date parseTimestamp(final String s)
354         throws ParseException
355  {
356    // First, try to parse the value as a generalized time.
357    try
358    {
359      return StaticUtils.decodeGeneralizedTime(s);
360    }
361    catch (final Exception e)
362    {
363      // This is fine.  It just means the value isn't in the generalized time
364      // format.
365    }
366
367
368    // See if the length of the string matches one of the supported local
369    // formats.  If so, get a format string that we can use to parse the value.
370    final String dateFormatString;
371    switch (s.length())
372    {
373      case 18:
374        dateFormatString = "yyyyMMddHHmmss.SSS";
375        break;
376      case 14:
377        dateFormatString = "yyyyMMddHHmmss";
378        break;
379      case 12:
380        dateFormatString = "yyyyMMddHHmm";
381        break;
382      default:
383        throw new ParseException(ERR_TIMESTAMP_PARSE_ERROR.get(s), 0);
384    }
385
386
387    // Create a date formatter that will use the selected format string to parse
388    // the timestamp.
389    final SimpleDateFormat dateFormat = new SimpleDateFormat(dateFormatString);
390    dateFormat.setLenient(false);
391    return dateFormat.parse(s);
392  }
393
394
395
396  /**
397   * Retrieves the value for this argument, or the default value if none was
398   * provided.  If there are multiple values, then the first will be returned.
399   *
400   * @return  The value for this argument, or the default value if none was
401   *          provided, or {@code null} if there is no value and no default
402   *          value.
403   */
404  public Date getValue()
405  {
406    if (values.isEmpty())
407    {
408      if ((defaultValues == null) || defaultValues.isEmpty())
409      {
410        return null;
411      }
412      else
413      {
414        return defaultValues.get(0);
415      }
416    }
417    else
418    {
419      return values.get(0).getFirst();
420    }
421  }
422
423
424
425  /**
426   * Retrieves the set of values for this argument.
427   *
428   * @return  The set of values for this argument.
429   */
430  public List<Date> getValues()
431  {
432    if (values.isEmpty() && (defaultValues != null))
433    {
434      return defaultValues;
435    }
436
437    final ArrayList<Date> dateList = new ArrayList<>(values.size());
438    for (final ObjectPair<Date,String> p : values)
439    {
440      dateList.add(p.getFirst());
441    }
442
443    return Collections.unmodifiableList(dateList);
444  }
445
446
447
448  /**
449   * Retrieves a string representation of the value for this argument, or a
450   * string representation of the default value if none was provided.  If there
451   * are multiple values, then the first will be returned.
452   *
453   * @return  The string representation of the value for this argument, or the
454   *          string representation of the default value if none was provided,
455   *          or {@code null} if there is no value and no default value.
456   */
457  public String getStringValue()
458  {
459    if (! values.isEmpty())
460    {
461      return values.get(0).getSecond();
462    }
463
464    if ((defaultValues != null) && (! defaultValues.isEmpty()))
465    {
466      return StaticUtils.encodeGeneralizedTime(defaultValues.get(0));
467    }
468
469    return null;
470  }
471
472
473
474  /**
475   * {@inheritDoc}
476   */
477  @Override()
478  public List<String> getValueStringRepresentations(final boolean useDefault)
479  {
480    if (! values.isEmpty())
481    {
482      final ArrayList<String> valueStrings = new ArrayList<>(values.size());
483      for (final ObjectPair<Date,String> p : values)
484      {
485        valueStrings.add(p.getSecond());
486      }
487
488      return Collections.unmodifiableList(valueStrings);
489    }
490
491    if (useDefault && (defaultValues != null) && (! defaultValues.isEmpty()))
492    {
493      final ArrayList<String> valueStrings =
494           new ArrayList<>(defaultValues.size());
495      for (final Date d : defaultValues)
496      {
497        valueStrings.add(StaticUtils.encodeGeneralizedTime(d));
498      }
499
500      return Collections.unmodifiableList(valueStrings);
501    }
502
503    return Collections.emptyList();
504  }
505
506
507
508  /**
509   * {@inheritDoc}
510   */
511  @Override()
512  protected boolean hasDefaultValue()
513  {
514    return ((defaultValues != null) && (! defaultValues.isEmpty()));
515  }
516
517
518
519  /**
520   * {@inheritDoc}
521   */
522  @Override()
523  public String getDataTypeName()
524  {
525    return INFO_TIMESTAMP_TYPE_NAME.get();
526  }
527
528
529
530  /**
531   * {@inheritDoc}
532   */
533  @Override()
534  public String getValueConstraints()
535  {
536    return INFO_TIMESTAMP_CONSTRAINTS.get();
537  }
538
539
540
541  /**
542   * {@inheritDoc}
543   */
544  @Override()
545  protected void reset()
546  {
547    super.reset();
548    values.clear();
549  }
550
551
552
553  /**
554   * {@inheritDoc}
555   */
556  @Override()
557  public TimestampArgument getCleanCopy()
558  {
559    return new TimestampArgument(this);
560  }
561
562
563
564  /**
565   * {@inheritDoc}
566   */
567  @Override()
568  protected void addToCommandLine(final List<String> argStrings)
569  {
570    if (values != null)
571    {
572      for (final ObjectPair<Date,String> p : values)
573      {
574        argStrings.add(getIdentifierString());
575        if (isSensitive())
576        {
577          argStrings.add("***REDACTED***");
578        }
579        else
580        {
581          argStrings.add(p.getSecond());
582        }
583      }
584    }
585  }
586
587
588
589  /**
590   * {@inheritDoc}
591   */
592  @Override()
593  public void toString(final StringBuilder buffer)
594  {
595    buffer.append("TimestampArgument(");
596    appendBasicToStringInfo(buffer);
597
598    if ((defaultValues != null) && (! defaultValues.isEmpty()))
599    {
600      if (defaultValues.size() == 1)
601      {
602        buffer.append(", defaultValue='");
603        buffer.append(StaticUtils.encodeGeneralizedTime(defaultValues.get(0)));
604      }
605      else
606      {
607        buffer.append(", defaultValues={");
608
609        final Iterator<Date> iterator = defaultValues.iterator();
610        while (iterator.hasNext())
611        {
612          buffer.append('\'');
613          buffer.append(StaticUtils.encodeGeneralizedTime(iterator.next()));
614          buffer.append('\'');
615
616          if (iterator.hasNext())
617          {
618            buffer.append(", ");
619          }
620        }
621
622        buffer.append('}');
623      }
624    }
625
626    buffer.append(')');
627  }
628}