You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AbstractDateField.java 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
  1. /*
  2. * Copyright 2000-2016 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.ui;
  17. import java.io.Serializable;
  18. import java.lang.reflect.Type;
  19. import java.text.SimpleDateFormat;
  20. import java.time.LocalDate;
  21. import java.time.temporal.Temporal;
  22. import java.time.temporal.TemporalAdjuster;
  23. import java.util.Calendar;
  24. import java.util.Date;
  25. import java.util.EventObject;
  26. import java.util.HashMap;
  27. import java.util.Locale;
  28. import java.util.Map;
  29. import java.util.Optional;
  30. import java.util.Set;
  31. import java.util.logging.Logger;
  32. import java.util.stream.Collectors;
  33. import java.util.stream.Stream;
  34. import org.jsoup.nodes.Element;
  35. import com.googlecode.gentyref.GenericTypeReflector;
  36. import com.vaadin.data.Result;
  37. import com.vaadin.data.ValidationResult;
  38. import com.vaadin.data.ValueContext;
  39. import com.vaadin.data.validator.RangeValidator;
  40. import com.vaadin.event.FieldEvents.BlurEvent;
  41. import com.vaadin.event.FieldEvents.BlurListener;
  42. import com.vaadin.event.FieldEvents.BlurNotifier;
  43. import com.vaadin.event.FieldEvents.FocusEvent;
  44. import com.vaadin.event.FieldEvents.FocusListener;
  45. import com.vaadin.event.FieldEvents.FocusNotifier;
  46. import com.vaadin.server.PaintException;
  47. import com.vaadin.server.PaintTarget;
  48. import com.vaadin.server.UserError;
  49. import com.vaadin.shared.Registration;
  50. import com.vaadin.shared.ui.datefield.AbstractDateFieldState;
  51. import com.vaadin.shared.ui.datefield.DateFieldConstants;
  52. import com.vaadin.shared.ui.datefield.DateResolution;
  53. import com.vaadin.ui.declarative.DesignAttributeHandler;
  54. import com.vaadin.ui.declarative.DesignContext;
  55. /**
  56. * A date editor component with {@link LocalDate} as an input value.
  57. *
  58. * @author Vaadin Ltd
  59. *
  60. * @since 8.0
  61. *
  62. * @param <T>
  63. * type of date ({@code LocalDate} or {@code LocalDateTime}).
  64. * @param <R>
  65. * resolution enumeration type
  66. *
  67. */
  68. public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & Serializable & Comparable<? super T>, R extends Enum<R>>
  69. extends AbstractField<T>
  70. implements LegacyComponent, FocusNotifier, BlurNotifier {
  71. /**
  72. * Value of the field.
  73. */
  74. private T value;
  75. /**
  76. * Specified smallest modifiable unit for the date field.
  77. */
  78. private R resolution;
  79. /**
  80. * Overridden format string
  81. */
  82. private String dateFormat;
  83. private boolean lenient = false;
  84. private String dateString = null;
  85. private String currentParseErrorMessage;
  86. /**
  87. * Was the last entered string parsable? If this flag is false, datefields
  88. * internal validator does not pass.
  89. */
  90. private boolean uiHasValidDateString = true;
  91. /**
  92. * Determines if week numbers are shown in the date selector.
  93. */
  94. private boolean showISOWeekNumbers = false;
  95. private String defaultParseErrorMessage = "Date format not recognized";
  96. private String dateOutOfRangeMessage = "Date is out of allowed range";
  97. /**
  98. * Determines whether the ValueChangeEvent should be fired. Used to prevent
  99. * firing the event when UI has invalid string until uiHasValidDateString
  100. * flag is set
  101. */
  102. private boolean preventValueChangeEvent;
  103. /* Constructors */
  104. /**
  105. * Constructs an empty <code>AbstractDateField</code> with no caption and
  106. * specified {@code resolution}.
  107. *
  108. * @param resolution
  109. * initial resolution for the field
  110. */
  111. public AbstractDateField(R resolution) {
  112. this.resolution = resolution;
  113. }
  114. /**
  115. * Constructs an empty <code>AbstractDateField</code> with caption.
  116. *
  117. * @param caption
  118. * the caption of the datefield.
  119. * @param resolution
  120. * initial resolution for the field
  121. */
  122. public AbstractDateField(String caption, R resolution) {
  123. this(resolution);
  124. setCaption(caption);
  125. }
  126. /**
  127. * Constructs a new <code>AbstractDateField</code> with the given caption
  128. * and initial text contents.
  129. *
  130. * @param caption
  131. * the caption <code>String</code> for the editor.
  132. * @param value
  133. * the date/time value.
  134. * @param resolution
  135. * initial resolution for the field
  136. */
  137. public AbstractDateField(String caption, T value, R resolution) {
  138. this(caption, resolution);
  139. setValue(value);
  140. }
  141. /* Component basic features */
  142. /*
  143. * Paints this component. Don't add a JavaDoc comment here, we use the
  144. * default documentation from implemented interface.
  145. */
  146. @Override
  147. public void paintContent(PaintTarget target) throws PaintException {
  148. // Adds the locale as attribute
  149. final Locale l = getLocale();
  150. if (l != null) {
  151. target.addAttribute("locale", l.toString());
  152. }
  153. if (getDateFormat() != null) {
  154. target.addAttribute("format", getDateFormat());
  155. }
  156. if (!isLenient()) {
  157. target.addAttribute("strict", true);
  158. }
  159. target.addAttribute(DateFieldConstants.ATTR_WEEK_NUMBERS,
  160. isShowISOWeekNumbers());
  161. target.addAttribute("parsable", uiHasValidDateString);
  162. /*
  163. * TODO communicate back the invalid date string? E.g. returning back to
  164. * app or refresh.
  165. */
  166. final T currentDate = getValue();
  167. // Only paint variables for the resolution and up, e.g. Resolution DAY
  168. // paints DAY,MONTH,YEAR
  169. for (R res : getResolutionsHigherOrEqualTo(getResolution())) {
  170. int value = -1;
  171. if (currentDate != null) {
  172. value = getDatePart(currentDate, res);
  173. }
  174. target.addVariable(this, getResolutionVariable(res), value);
  175. }
  176. }
  177. /*
  178. * Invoked when a variable of the component changes. Don't add a JavaDoc
  179. * comment here, we use the default documentation from implemented
  180. * interface.
  181. */
  182. @Override
  183. public void changeVariables(Object source, Map<String, Object> variables) {
  184. Set<String> resolutionNames = getResolutions()
  185. .map(this::getResolutionVariable).collect(Collectors.toSet());
  186. resolutionNames.retainAll(variables.keySet());
  187. if (!isReadOnly() && (!resolutionNames.isEmpty()
  188. || variables.containsKey("dateString"))) {
  189. // Old and new dates
  190. final T oldDate = getValue();
  191. T newDate = null;
  192. // this enables analyzing invalid input on the server
  193. final String newDateString = (String) variables.get("dateString");
  194. dateString = newDateString;
  195. // Gets the new date in parts
  196. boolean hasChanges = false;
  197. Map<R, Integer> calendarFields = new HashMap<>();
  198. for (R resolution : getResolutionsHigherOrEqualTo(
  199. getResolution())) {
  200. // Only handle what the client is allowed to send. The same
  201. // resolutions that are painted
  202. String variableName = getResolutionVariable(resolution);
  203. int value = getDatePart(oldDate, resolution);
  204. if (variables.containsKey(variableName)) {
  205. Integer newValue = (Integer) variables.get(variableName);
  206. if (newValue >= 0) {
  207. hasChanges = true;
  208. value = newValue;
  209. }
  210. }
  211. calendarFields.put(resolution, value);
  212. }
  213. // If no new variable values were received, use the previous value
  214. if (!hasChanges) {
  215. newDate = null;
  216. } else {
  217. newDate = buildDate(calendarFields);
  218. }
  219. if (newDate == null && dateString != null
  220. && !dateString.isEmpty()) {
  221. Result<T> parsedDate = handleUnparsableDateString(dateString);
  222. if (parsedDate.isError()) {
  223. /*
  224. * Saves the localized message of parse error. This can be
  225. * overridden in handleUnparsableDateString. The message
  226. * will later be used to show a validation error.
  227. */
  228. currentParseErrorMessage = parsedDate.getMessage().get();
  229. /*
  230. * The value of the DateField should be null if an invalid
  231. * value has been given. Not using setValue() since we do
  232. * not want to cause the client side value to change.
  233. */
  234. uiHasValidDateString = false;
  235. /*
  236. * Datefield now contains some text that could't be parsed
  237. * into date. ValueChangeEvent is fired after the value is
  238. * changed and the flags are set
  239. */
  240. if (oldDate != null) {
  241. /*
  242. * Set the logic value to null without firing the
  243. * ValueChangeEvent
  244. */
  245. preventValueChangeEvent = true;
  246. try {
  247. setValue(null);
  248. } finally {
  249. preventValueChangeEvent = false;
  250. }
  251. /*
  252. * Reset the dateString (overridden to null by setValue)
  253. */
  254. dateString = newDateString;
  255. }
  256. /*
  257. * If value was changed fire the ValueChangeEvent
  258. */
  259. if (oldDate != null) {
  260. fireEvent(createValueChange(oldDate, true));
  261. }
  262. markAsDirty();
  263. } else {
  264. parsedDate.ifOk(value -> setValue(value, true));
  265. /*
  266. * Ensure the value is sent to the client if the value is
  267. * set to the same as the previous (#4304). Does not repaint
  268. * if handleUnparsableDateString throws an exception. In
  269. * this case the invalid text remains in the DateField.
  270. */
  271. markAsDirty();
  272. }
  273. } else if (newDate != oldDate
  274. && (newDate == null || !newDate.equals(oldDate))) {
  275. setValue(newDate, true); // Don't require a repaint, client
  276. // updates itself
  277. } else if (!uiHasValidDateString) {
  278. // oldDate ==
  279. // newDate == null
  280. // Empty value set, previously contained unparsable date string,
  281. // clear related internal fields
  282. setValue(null);
  283. }
  284. }
  285. if (variables.containsKey(FocusEvent.EVENT_ID)) {
  286. fireEvent(new FocusEvent(this));
  287. }
  288. if (variables.containsKey(BlurEvent.EVENT_ID)) {
  289. fireEvent(new BlurEvent(this));
  290. }
  291. }
  292. /**
  293. * Sets the start range for this component. If the value is set before this
  294. * date (taking the resolution into account), the component will not
  295. * validate. If <code>startDate</code> is set to <code>null</code>, any
  296. * value before <code>endDate</code> will be accepted by the range
  297. *
  298. * @param startDate
  299. * - the allowed range's start date
  300. */
  301. public void setRangeStart(T startDate) {
  302. Date date = convertToDate(startDate);
  303. if (date != null && getState().rangeEnd != null
  304. && date.after(getState().rangeEnd)) {
  305. throw new IllegalStateException(
  306. "startDate cannot be later than endDate");
  307. }
  308. getState().rangeStart = date;
  309. }
  310. /**
  311. * Sets the current error message if the range validation fails.
  312. *
  313. * @param dateOutOfRangeMessage
  314. * - Localizable message which is shown when value (the date) is
  315. * set outside allowed range
  316. */
  317. public void setDateOutOfRangeMessage(String dateOutOfRangeMessage) {
  318. this.dateOutOfRangeMessage = dateOutOfRangeMessage;
  319. }
  320. /**
  321. * Returns current date-out-of-range error message.
  322. *
  323. * @see #setDateOutOfRangeMessage(String)
  324. * @return Current error message for dates out of range.
  325. */
  326. public String getDateOutOfRangeMessage() {
  327. return dateOutOfRangeMessage;
  328. }
  329. /**
  330. * Gets the resolution.
  331. *
  332. * @return the date/time field resolution
  333. */
  334. public R getResolution() {
  335. return resolution;
  336. }
  337. /**
  338. * Sets the resolution of the DateField.
  339. *
  340. * The default resolution is {@link DateResolution#DAY} since Vaadin 7.0.
  341. *
  342. * @param resolution
  343. * the resolution to set, not {@code null}
  344. */
  345. public void setResolution(R resolution) {
  346. this.resolution = resolution;
  347. markAsDirty();
  348. }
  349. /**
  350. * Sets the end range for this component. If the value is set after this
  351. * date (taking the resolution into account), the component will not
  352. * validate. If <code>endDate</code> is set to <code>null</code>, any value
  353. * after <code>startDate</code> will be accepted by the range.
  354. *
  355. * @param endDate
  356. * - the allowed range's end date (inclusive, based on the
  357. * current resolution)
  358. */
  359. public void setRangeEnd(T endDate) {
  360. Date date = convertToDate(endDate);
  361. if (date != null && getState().rangeStart != null
  362. && getState().rangeStart.after(date)) {
  363. throw new IllegalStateException(
  364. "endDate cannot be earlier than startDate");
  365. }
  366. getState().rangeEnd = date;
  367. }
  368. /**
  369. * Returns the precise rangeStart used.
  370. *
  371. * @return the precise rangeStart used, may be null.
  372. */
  373. public T getRangeStart() {
  374. return convertFromDate(getState(false).rangeStart);
  375. }
  376. /**
  377. * Returns the precise rangeEnd used.
  378. *
  379. * @return the precise rangeEnd used, may be null.
  380. */
  381. public T getRangeEnd() {
  382. return convertFromDate(getState(false).rangeEnd);
  383. }
  384. /**
  385. * Sets formatting used by some component implementations. See
  386. * {@link SimpleDateFormat} for format details.
  387. *
  388. * By default it is encouraged to used default formatting defined by Locale,
  389. * but due some JVM bugs it is sometimes necessary to use this method to
  390. * override formatting. See Vaadin issue #2200.
  391. *
  392. * @param dateFormat
  393. * the dateFormat to set
  394. *
  395. * @see com.vaadin.ui.AbstractComponent#setLocale(Locale))
  396. */
  397. public void setDateFormat(String dateFormat) {
  398. this.dateFormat = dateFormat;
  399. markAsDirty();
  400. }
  401. /**
  402. * Returns a format string used to format date value on client side or null
  403. * if default formatting from {@link Component#getLocale()} is used.
  404. *
  405. * @return the dateFormat
  406. */
  407. public String getDateFormat() {
  408. return dateFormat;
  409. }
  410. /**
  411. * Specifies whether or not date/time interpretation in component is to be
  412. * lenient.
  413. *
  414. * @see Calendar#setLenient(boolean)
  415. * @see #isLenient()
  416. *
  417. * @param lenient
  418. * true if the lenient mode is to be turned on; false if it is to
  419. * be turned off.
  420. */
  421. public void setLenient(boolean lenient) {
  422. this.lenient = lenient;
  423. markAsDirty();
  424. }
  425. /**
  426. * Returns whether date/time interpretation is to be lenient.
  427. *
  428. * @see #setLenient(boolean)
  429. *
  430. * @return true if the interpretation mode of this calendar is lenient;
  431. * false otherwise.
  432. */
  433. public boolean isLenient() {
  434. return lenient;
  435. }
  436. @Override
  437. public T getValue() {
  438. return value;
  439. }
  440. /**
  441. * Sets the value of this object. If the new value is not equal to
  442. * {@code getValue()}, fires a {@link ValueChangeEvent} .
  443. *
  444. * @param value
  445. * the new value, may be {@code null}
  446. */
  447. @Override
  448. public void setValue(T value) {
  449. /*
  450. * First handle special case when the client side component have a date
  451. * string but value is null (e.g. unparsable date string typed in by the
  452. * user). No value changes should happen, but we need to do some
  453. * internal housekeeping.
  454. */
  455. if (value == null && !uiHasValidDateString) {
  456. /*
  457. * Side-effects of doSetValue clears possible previous strings and
  458. * flags about invalid input.
  459. */
  460. doSetValue(null);
  461. markAsDirty();
  462. return;
  463. }
  464. super.setValue(value);
  465. }
  466. /**
  467. * Checks whether ISO 8601 week numbers are shown in the date selector.
  468. *
  469. * @return true if week numbers are shown, false otherwise.
  470. */
  471. public boolean isShowISOWeekNumbers() {
  472. return showISOWeekNumbers;
  473. }
  474. /**
  475. * Sets the visibility of ISO 8601 week numbers in the date selector. ISO
  476. * 8601 defines that a week always starts with a Monday so the week numbers
  477. * are only shown if this is the case.
  478. *
  479. * @param showWeekNumbers
  480. * true if week numbers should be shown, false otherwise.
  481. */
  482. public void setShowISOWeekNumbers(boolean showWeekNumbers) {
  483. showISOWeekNumbers = showWeekNumbers;
  484. markAsDirty();
  485. }
  486. /**
  487. * Return the error message that is shown if the user inputted value can't
  488. * be parsed into a Date object. If
  489. * {@link #handleUnparsableDateString(String)} is overridden and it throws a
  490. * custom exception, the message returned by
  491. * {@link Exception#getLocalizedMessage()} will be used instead of the value
  492. * returned by this method.
  493. *
  494. * @see #setParseErrorMessage(String)
  495. *
  496. * @return the error message that the DateField uses when it can't parse the
  497. * textual input from user to a Date object
  498. */
  499. public String getParseErrorMessage() {
  500. return defaultParseErrorMessage;
  501. }
  502. /**
  503. * Sets the default error message used if the DateField cannot parse the
  504. * text input by user to a Date field. Note that if the
  505. * {@link #handleUnparsableDateString(String)} method is overridden, the
  506. * localized message from its exception is used.
  507. *
  508. * @see #getParseErrorMessage()
  509. * @see #handleUnparsableDateString(String)
  510. * @param parsingErrorMessage
  511. */
  512. public void setParseErrorMessage(String parsingErrorMessage) {
  513. defaultParseErrorMessage = parsingErrorMessage;
  514. }
  515. @Override
  516. public Registration addFocusListener(FocusListener listener) {
  517. return addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
  518. FocusListener.focusMethod);
  519. }
  520. @Override
  521. public Registration addBlurListener(BlurListener listener) {
  522. return addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
  523. BlurListener.blurMethod);
  524. }
  525. @Override
  526. @SuppressWarnings("unchecked")
  527. public void readDesign(Element design, DesignContext designContext) {
  528. super.readDesign(design, designContext);
  529. if (design.hasAttr("value") && !design.attr("value").isEmpty()) {
  530. Type dateType = GenericTypeReflector.getTypeParameter(getClass(),
  531. AbstractDateField.class.getTypeParameters()[0]);
  532. if (dateType instanceof Class<?>) {
  533. Class<?> clazz = (Class<?>) dateType;
  534. T date = (T) DesignAttributeHandler.getFormatter()
  535. .parse(design.attr("value"), clazz);
  536. // formatting will return null if it cannot parse the string
  537. if (date == null) {
  538. Logger.getLogger(AbstractDateField.class.getName())
  539. .info("cannot parse " + design.attr("value")
  540. + " as date");
  541. }
  542. doSetValue(date);
  543. } else {
  544. throw new RuntimeException("Cannot detect resoluton type "
  545. + Optional.ofNullable(dateType).map(Type::getTypeName)
  546. .orElse(null));
  547. }
  548. }
  549. }
  550. @Override
  551. public void writeDesign(Element design, DesignContext designContext) {
  552. super.writeDesign(design, designContext);
  553. if (getValue() != null) {
  554. design.attr("value",
  555. DesignAttributeHandler.getFormatter().format(getValue()));
  556. }
  557. }
  558. @Override
  559. protected void fireEvent(EventObject event) {
  560. if (event instanceof ValueChangeEvent) {
  561. if (!preventValueChangeEvent) {
  562. super.fireEvent(event);
  563. }
  564. } else {
  565. super.fireEvent(event);
  566. }
  567. }
  568. /**
  569. * This method is called to handle a non-empty date string from the client
  570. * if the client could not parse it as a Date.
  571. *
  572. * By default, an error result is returned whose error message is
  573. * {@link #getParseErrorMessage()}.
  574. *
  575. * This can be overridden to handle conversions, to return a result with
  576. * {@code null} value (equivalent to empty input) or to return a custom
  577. * error.
  578. *
  579. * @param dateString
  580. * date string to handle
  581. * @return result that contains parsed Date as a value or an error
  582. */
  583. protected Result<T> handleUnparsableDateString(String dateString) {
  584. return Result.error(getParseErrorMessage());
  585. }
  586. @Override
  587. protected AbstractDateFieldState getState() {
  588. return (AbstractDateFieldState) super.getState();
  589. }
  590. @Override
  591. protected AbstractDateFieldState getState(boolean markAsDirty) {
  592. return (AbstractDateFieldState) super.getState(markAsDirty);
  593. }
  594. @Override
  595. protected void doSetValue(T value) {
  596. // Also set the internal dateString
  597. if (value != null) {
  598. dateString = value.toString();
  599. } else {
  600. dateString = null;
  601. }
  602. this.value = value;
  603. setComponentError(null);
  604. if (!uiHasValidDateString) {
  605. // clear component error and parsing flag
  606. uiHasValidDateString = true;
  607. setComponentError(new UserError(currentParseErrorMessage));
  608. } else {
  609. RangeValidator<T> validator = getRangeValidator();
  610. ValidationResult result = validator.apply(value,
  611. new ValueContext(this));
  612. if (result.isError()) {
  613. setComponentError(new UserError(getDateOutOfRangeMessage()));
  614. }
  615. }
  616. }
  617. /**
  618. * Returns a date integer value part for the given {@code date} for the
  619. * given {@code resolution}.
  620. *
  621. * @param date
  622. * the given date
  623. * @param resolution
  624. * the resolution to extract a value from the date by
  625. * @return the integer value part of the date by the given resolution
  626. */
  627. protected abstract int getDatePart(T date, R resolution);
  628. /**
  629. * Builds date by the given {@code resolutionValues} which is a map whose
  630. * keys are resolution and integer values.
  631. * <p>
  632. * This is the opposite to {@link #getDatePart(Temporal, Enum)}.
  633. *
  634. * @param resolutionValues
  635. * date values to construct a date
  636. * @return date built from the given map of date values
  637. */
  638. protected abstract T buildDate(Map<R, Integer> resolutionValues);
  639. /**
  640. * Returns a custom date range validator which is applicable for the type
  641. * {@code T}.
  642. *
  643. * @return the date range validator
  644. */
  645. protected abstract RangeValidator<T> getRangeValidator();
  646. /**
  647. * Converts {@link Date} to date type {@code T}.
  648. *
  649. * @param date
  650. * a date to convert
  651. * @return object of type {@code T} representing the {@code date}
  652. */
  653. protected abstract T convertFromDate(Date date);
  654. /**
  655. * Converts the object of type {@code T} to {@link Date}.
  656. * <p>
  657. * This is the opposite to {@link #convertFromDate(Date)}.
  658. *
  659. * @param date
  660. * the date of type {@code T}
  661. * @return converted date of type {@code Date}
  662. */
  663. protected abstract Date convertToDate(T date);
  664. private String getResolutionVariable(R resolution) {
  665. return resolution.name().toLowerCase(Locale.ENGLISH);
  666. }
  667. @SuppressWarnings("unchecked")
  668. private Stream<R> getResolutions() {
  669. Type resolutionType = GenericTypeReflector.getTypeParameter(getClass(),
  670. AbstractDateField.class.getTypeParameters()[1]);
  671. if (resolutionType instanceof Class<?>) {
  672. Class<?> clazz = (Class<?>) resolutionType;
  673. return Stream.of(clazz.getEnumConstants())
  674. .map(object -> (R) object);
  675. } else {
  676. throw new RuntimeException("Cannot detect resoluton type "
  677. + Optional.ofNullable(resolutionType).map(Type::getTypeName)
  678. .orElse(null));
  679. }
  680. }
  681. private Iterable<R> getResolutionsHigherOrEqualTo(R resoution) {
  682. return getResolutions().skip(resolution.ordinal())
  683. .collect(Collectors.toList());
  684. }
  685. }