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 }