001/*
002 * Copyright 2016-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2016-2017 UnboundID Corp.
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.Arrays;
029import java.util.Date;
030import java.util.Collections;
031import java.util.Iterator;
032import java.util.List;
033
034import com.unboundid.util.Debug;
035import com.unboundid.util.Mutable;
036import com.unboundid.util.ObjectPair;
037import com.unboundid.util.StaticUtils;
038import com.unboundid.util.ThreadSafety;
039import com.unboundid.util.ThreadSafetyLevel;
040
041import static com.unboundid.util.args.ArgsMessages.*;
042
043
044
045/**
046 * This class defines an argument that is intended to hold one or more
047 * timestamp values.  Values may be provided in any of the following formats:
048 * <UL>
049 *   <LI>Any valid generalized time format.</LI>
050 *   <LI>A local time zone timestamp in the format YYYYMMDDhhmmss.uuu</LI>
051 *   <LI>A local time zone timestamp in the format YYYYMMDDhhmmss</LI>
052 *   <LI>A local time zone timestamp in the format YYYYMMDDhhmm</LI>
053 * </UL>
054 */
055@Mutable()
056@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
057public final class TimestampArgument
058       extends Argument
059{
060  /**
061   * The serial version UID for this serializable class.
062   */
063  private static final long serialVersionUID = -4842934851103696096L;
064
065
066
067  // The argument value validators that have been registered for this argument.
068  private final List<ArgumentValueValidator> validators;
069
070  // The list of default values for this argument.
071  private final List<Date> defaultValues;
072
073  // The set of values assigned to this argument.
074  private final List<ObjectPair<Date,String>> values;
075
076
077
078  /**
079   * Creates a new timestamp argument with the provided information.  It will
080   * not be required, will permit at most one occurrence, will use a default
081   * placeholder, and will not have a default value.
082   *
083   * @param  shortIdentifier   The short identifier for this argument.  It may
084   *                           not be {@code null} if the long identifier is
085   *                           {@code null}.
086   * @param  longIdentifier    The long identifier for this argument.  It may
087   *                           not be {@code null} if the short identifier is
088   *                           {@code null}.
089   * @param  description       A human-readable description for this argument.
090   *                           It must not be {@code null}.
091   *
092   * @throws  ArgumentException  If there is a problem with the definition of
093   *                             this argument.
094   */
095  public TimestampArgument(final Character shortIdentifier,
096                           final String longIdentifier,
097                           final String description)
098         throws ArgumentException
099  {
100    this(shortIdentifier, longIdentifier, false, 1, null, description);
101  }
102
103
104
105  /**
106   * Creates a new timestamp argument with the provided information.  It will
107   * not have a default value.
108   *
109   * @param  shortIdentifier   The short identifier for this argument.  It may
110   *                           not be {@code null} if the long identifier is
111   *                           {@code null}.
112   * @param  longIdentifier    The long identifier for this argument.  It may
113   *                           not be {@code null} if the short identifier is
114   *                           {@code null}.
115   * @param  isRequired        Indicates whether this argument is required to
116   *                           be provided.
117   * @param  maxOccurrences    The maximum number of times this argument may be
118   *                           provided on the command line.  A value less than
119   *                           or equal to zero indicates that it may be present
120   *                           any number of times.
121   * @param  valuePlaceholder  A placeholder to display in usage information to
122   *                           indicate that a value must be provided.  It may
123   *                           be {@code null} if a default placeholder should
124   *                           be used.
125   * @param  description       A human-readable description for this argument.
126   *                           It must not be {@code null}.
127   *
128   * @throws  ArgumentException  If there is a problem with the definition of
129   *                             this argument.
130   */
131  public TimestampArgument(final Character shortIdentifier,
132                           final String longIdentifier,
133                           final boolean isRequired, final int maxOccurrences,
134                           final String valuePlaceholder,
135                           final String description)
136         throws ArgumentException
137  {
138    this(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
139         valuePlaceholder, description, (List<Date>) null);
140  }
141
142
143
144  /**
145   * Creates a new timestamp argument with the provided information.
146   *
147   * @param  shortIdentifier   The short identifier for this argument.  It may
148   *                           not be {@code null} if the long identifier is
149   *                           {@code null}.
150   * @param  longIdentifier    The long identifier for this argument.  It may
151   *                           not be {@code null} if the short identifier is
152   *                           {@code null}.
153   * @param  isRequired        Indicates whether this argument is required to
154   *                           be provided.
155   * @param  maxOccurrences    The maximum number of times this argument may be
156   *                           provided on the command line.  A value less than
157   *                           or equal to zero indicates that it may be present
158   *                           any number of times.
159   * @param  valuePlaceholder  A placeholder to display in usage information to
160   *                           indicate that a value must be provided.  It may
161   *                           be {@code null} if a default placeholder should
162   *                           be used.
163   * @param  description       A human-readable description for this argument.
164   *                           It must not be {@code null}.
165   * @param  defaultValue      The default value to use for this argument if no
166   *                           values were provided.
167   *
168   * @throws  ArgumentException  If there is a problem with the definition of
169   *                             this argument.
170   */
171  public TimestampArgument(final Character shortIdentifier,
172                           final String longIdentifier,
173                           final boolean isRequired, final int maxOccurrences,
174                           final String valuePlaceholder,
175                           final String description, final Date defaultValue)
176         throws ArgumentException
177  {
178    this(shortIdentifier, longIdentifier, isRequired, maxOccurrences,
179         valuePlaceholder, description,
180         ((defaultValue == null) ? null : Arrays.asList(defaultValue)));
181  }
182
183
184
185  /**
186   * Creates a new timestamp argument with the provided information.
187   *
188   * @param  shortIdentifier   The short identifier for this argument.  It may
189   *                           not be {@code null} if the long identifier is
190   *                           {@code null}.
191   * @param  longIdentifier    The long identifier for this argument.  It may
192   *                           not be {@code null} if the short identifier is
193   *                           {@code null}.
194   * @param  isRequired        Indicates whether this argument is required to
195   *                           be provided.
196   * @param  maxOccurrences    The maximum number of times this argument may be
197   *                           provided on the command line.  A value less than
198   *                           or equal to zero indicates that it may be present
199   *                           any number of times.
200   * @param  valuePlaceholder  A placeholder to display in usage information to
201   *                           indicate that a value must be provided.  It may
202   *                           be {@code null} if a default placeholder should
203   *                           be used.
204   * @param  description       A human-readable description for this argument.
205   *                           It must not be {@code null}.
206   * @param  defaultValues     The set of default values to use for this
207   *                           argument if no values were provided.
208   *
209   * @throws  ArgumentException  If there is a problem with the definition of
210   *                             this argument.
211   */
212  public TimestampArgument(final Character shortIdentifier,
213                           final String longIdentifier,
214                           final boolean isRequired, final int maxOccurrences,
215                           final String valuePlaceholder,
216                           final String description,
217                           final List<Date> defaultValues)
218         throws ArgumentException
219  {
220    super(shortIdentifier, longIdentifier, isRequired,  maxOccurrences,
221         (valuePlaceholder == null)
222              ? INFO_PLACEHOLDER_TIMESTAMP.get()
223              : valuePlaceholder,
224         description);
225
226    if ((defaultValues == null) || defaultValues.isEmpty())
227    {
228      this.defaultValues = null;
229    }
230    else
231    {
232      this.defaultValues = Collections.unmodifiableList(defaultValues);
233    }
234
235    values = new ArrayList<ObjectPair<Date,String>>(5);
236    validators = new ArrayList<ArgumentValueValidator>(5);
237  }
238
239
240
241  /**
242   * Creates a new timestamp argument that is a "clean" copy of the provided
243   * source argument.
244   *
245   * @param  source  The source argument to use for this argument.
246   */
247  private TimestampArgument(final TimestampArgument source)
248  {
249    super(source);
250
251    defaultValues = source.defaultValues;
252    values        = new ArrayList<ObjectPair<Date,String>>(5);
253    validators    = new ArrayList<ArgumentValueValidator>(source.validators);
254  }
255
256
257
258  /**
259   * Retrieves the list of default values for this argument, which will be used
260   * if no values were provided.
261   *
262   * @return   The list of default values for this argument, or {@code null} if
263   *           there are no default values.
264   */
265  public List<Date> getDefaultValues()
266  {
267    return defaultValues;
268  }
269
270
271
272  /**
273   * Updates this argument to ensure that the provided validator will be invoked
274   * for any values provided to this argument.  This validator will be invoked
275   * after all other validation has been performed for this argument.
276   *
277   * @param  validator  The argument value validator to be invoked.  It must not
278   *                    be {@code null}.
279   */
280  public void addValueValidator(final ArgumentValueValidator validator)
281  {
282    validators.add(validator);
283  }
284
285
286
287  /**
288   * {@inheritDoc}
289   */
290  @Override()
291  protected void addValue(final String valueString)
292            throws ArgumentException
293  {
294    final Date d;
295    try
296    {
297      d = parseTimestamp(valueString);
298    }
299    catch (final Exception e)
300    {
301      Debug.debugException(e);
302      throw new ArgumentException(
303           ERR_TIMESTAMP_VALUE_NOT_TIMESTAMP.get(valueString,
304                getIdentifierString()),
305           e);
306    }
307
308
309    if (values.size() >= getMaxOccurrences())
310    {
311      throw new ArgumentException(ERR_ARG_MAX_OCCURRENCES_EXCEEDED.get(
312                                       getIdentifierString()));
313    }
314
315    for (final ArgumentValueValidator v : validators)
316    {
317      v.validateArgumentValue(this, valueString);
318    }
319
320    values.add(new ObjectPair<Date,String>(d, valueString));
321  }
322
323
324
325  /**
326   * Parses the provided string as a timestamp using one of the supported
327   * formats.
328   *
329   * @param  s  The string to parse as a timestamp.  It must not be
330   *            {@code null}.
331   *
332   * @return  The {@code Date} object parsed from the provided timestamp.
333   *
334   * @throws  ParseException  If the provided string cannot be parsed as a
335   *                          timestamp.
336   */
337  public static Date parseTimestamp(final String s)
338         throws ParseException
339  {
340    // First, try to parse the value as a generalized time.
341    try
342    {
343      return StaticUtils.decodeGeneralizedTime(s);
344    }
345    catch (final Exception e)
346    {
347      // This is fine.  It just means the value isn't in the generalized time
348      // format.
349    }
350
351
352    // See if the length of the string matches one of the supported local
353    // formats.  If so, get a format string that we can use to parse the value.
354    final String dateFormatString;
355    switch (s.length())
356    {
357      case 18:
358        dateFormatString = "yyyyMMddHHmmss.SSS";
359        break;
360      case 14:
361        dateFormatString = "yyyyMMddHHmmss";
362        break;
363      case 12:
364        dateFormatString = "yyyyMMddHHmm";
365        break;
366      default:
367        throw new ParseException(ERR_TIMESTAMP_PARSE_ERROR.get(s), 0);
368    }
369
370
371    // Configure the
372    final SimpleDateFormat dateFormat = new SimpleDateFormat(dateFormatString);
373    dateFormat.setLenient(false);
374    return dateFormat.parse(s);
375  }
376
377
378
379  /**
380   * Retrieves the value for this argument, or the default value if none was
381   * provided.  If there are multiple values, then the first will be returned.
382   *
383   * @return  The value for this argument, or the default value if none was
384   *          provided, or {@code null} if there is no value and no default
385   *          value.
386   */
387  public Date getValue()
388  {
389    if (values.isEmpty())
390    {
391      if ((defaultValues == null) || defaultValues.isEmpty())
392      {
393        return null;
394      }
395      else
396      {
397        return defaultValues.get(0);
398      }
399    }
400    else
401    {
402      return values.get(0).getFirst();
403    }
404  }
405
406
407
408  /**
409   * Retrieves the set of values for this argument.
410   *
411   * @return  The set of values for this argument.
412   */
413  public List<Date> getValues()
414  {
415    if (values.isEmpty() && (defaultValues != null))
416    {
417      return defaultValues;
418    }
419
420    final ArrayList<Date> dateList = new ArrayList<Date>(values.size());
421    for (final ObjectPair<Date,String> p : values)
422    {
423      dateList.add(p.getFirst());
424    }
425
426    return Collections.unmodifiableList(dateList);
427  }
428
429
430
431  /**
432   * Retrieves a string representation of the value for this argument, or a
433   * string representation of the default value if none was provided.  If there
434   * are multiple values, then the first will be returned.
435   *
436   * @return  The string representation of the value for this argument, or the
437   *          string representation of the default value if none was provided,
438   *          or {@code null} if there is no value and no default value.
439   */
440  public String getStringValue()
441  {
442    if (! values.isEmpty())
443    {
444      return values.get(0).getSecond();
445    }
446
447    if ((defaultValues != null) && (! defaultValues.isEmpty()))
448    {
449      return StaticUtils.encodeGeneralizedTime(defaultValues.get(0));
450    }
451
452    return null;
453  }
454
455
456
457  /**
458   * {@inheritDoc}
459   */
460  @Override()
461  public List<String> getValueStringRepresentations(final boolean useDefault)
462  {
463    if (! values.isEmpty())
464    {
465      final ArrayList<String> valueStrings =
466           new ArrayList<String>(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<String>(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}