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}