001/* 002 * Copyright 2016-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2016-2019 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}