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