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