001    /*
002     * Copyright 2016 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2016 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     */
021    package com.unboundid.util.args;
022    
023    
024    
025    import java.text.ParseException;
026    import java.text.SimpleDateFormat;
027    import java.util.ArrayList;
028    import java.util.Arrays;
029    import java.util.Date;
030    import java.util.Collections;
031    import java.util.Iterator;
032    import java.util.List;
033    
034    import com.unboundid.util.Debug;
035    import com.unboundid.util.Mutable;
036    import com.unboundid.util.ObjectPair;
037    import com.unboundid.util.StaticUtils;
038    import com.unboundid.util.ThreadSafety;
039    import com.unboundid.util.ThreadSafetyLevel;
040    
041    import 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)
057    public 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    }