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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769
  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(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) { // oldDate ==
  278. // newDate == null
  279. // Empty value set, previously contained unparsable date string,
  280. // clear related internal fields
  281. setValue(null);
  282. }
  283. }
  284. if (variables.containsKey(FocusEvent.EVENT_ID)) {
  285. fireEvent(new FocusEvent(this));
  286. }
  287. if (variables.containsKey(BlurEvent.EVENT_ID)) {
  288. fireEvent(new BlurEvent(this));
  289. }
  290. }
  291. /**
  292. * Sets the start range for this component. If the value is set before this
  293. * date (taking the resolution into account), the component will not
  294. * validate. If <code>startDate</code> is set to <code>null</code>, any
  295. * value before <code>endDate</code> will be accepted by the range
  296. *
  297. * @param startDate
  298. * - the allowed range's start date
  299. */
  300. public void setRangeStart(T startDate) {
  301. Date date = convertToDate(startDate);
  302. if (date != null && getState().rangeEnd != null
  303. && date.after(getState().rangeEnd)) {
  304. throw new IllegalStateException(
  305. "startDate cannot be later than endDate");
  306. }
  307. getState().rangeStart = date;
  308. }
  309. /**
  310. * Sets the current error message if the range validation fails.
  311. *
  312. * @param dateOutOfRangeMessage
  313. * - Localizable message which is shown when value (the date) is
  314. * set outside allowed range
  315. */
  316. public void setDateOutOfRangeMessage(String dateOutOfRangeMessage) {
  317. this.dateOutOfRangeMessage = dateOutOfRangeMessage;
  318. }
  319. /**
  320. * Returns current date-out-of-range error message.
  321. *
  322. * @see #setDateOutOfRangeMessage(String)
  323. * @return Current error message for dates out of range.
  324. */
  325. public String getDateOutOfRangeMessage() {
  326. return dateOutOfRangeMessage;
  327. }
  328. /**
  329. * Gets the resolution.
  330. *
  331. * @return the date/time field resolution
  332. */
  333. public R getResolution() {
  334. return resolution;
  335. }
  336. /**
  337. * Sets the resolution of the DateField.
  338. *
  339. * The default resolution is {@link DateResolution#DAY} since Vaadin 7.0.
  340. *
  341. * @param resolution
  342. * the resolution to set, not {@code null}
  343. */
  344. public void setResolution(R resolution) {
  345. this.resolution = resolution;
  346. markAsDirty();
  347. }
  348. /**
  349. * Sets the end range for this component. If the value is set after this
  350. * date (taking the resolution into account), the component will not
  351. * validate. If <code>endDate</code> is set to <code>null</code>, any value
  352. * after <code>startDate</code> will be accepted by the range.
  353. *
  354. * @param endDate
  355. * - the allowed range's end date (inclusive, based on the
  356. * current resolution)
  357. */
  358. public void setRangeEnd(T endDate) {
  359. Date date = convertToDate(endDate);
  360. if (date != null && getState().rangeStart != null
  361. && getState().rangeStart.after(date)) {
  362. throw new IllegalStateException(
  363. "endDate cannot be earlier than startDate");
  364. }
  365. getState().rangeEnd = date;
  366. }
  367. /**
  368. * Returns the precise rangeStart used.
  369. *
  370. * @return the precise rangeStart used, may be null.
  371. */
  372. public T getRangeStart() {
  373. return convertFromDate(getState(false).rangeStart);
  374. }
  375. /**
  376. * Returns the precise rangeEnd used.
  377. *
  378. * @return the precise rangeEnd used, may be null.
  379. */
  380. public T getRangeEnd() {
  381. return convertFromDate(getState(false).rangeEnd);
  382. }
  383. /**
  384. * Sets formatting used by some component implementations. See
  385. * {@link SimpleDateFormat} for format details.
  386. *
  387. * By default it is encouraged to used default formatting defined by Locale,
  388. * but due some JVM bugs it is sometimes necessary to use this method to
  389. * override formatting. See Vaadin issue #2200.
  390. *
  391. * @param dateFormat
  392. * the dateFormat to set
  393. *
  394. * @see com.vaadin.ui.AbstractComponent#setLocale(Locale))
  395. */
  396. public void setDateFormat(String dateFormat) {
  397. this.dateFormat = dateFormat;
  398. markAsDirty();
  399. }
  400. /**
  401. * Returns a format string used to format date value on client side or null
  402. * if default formatting from {@link Component#getLocale()} is used.
  403. *
  404. * @return the dateFormat
  405. */
  406. public String getDateFormat() {
  407. return dateFormat;
  408. }
  409. /**
  410. * Specifies whether or not date/time interpretation in component is to be
  411. * lenient.
  412. *
  413. * @see Calendar#setLenient(boolean)
  414. * @see #isLenient()
  415. *
  416. * @param lenient
  417. * true if the lenient mode is to be turned on; false if it is to
  418. * be turned off.
  419. */
  420. public void setLenient(boolean lenient) {
  421. this.lenient = lenient;
  422. markAsDirty();
  423. }
  424. /**
  425. * Returns whether date/time interpretation is to be lenient.
  426. *
  427. * @see #setLenient(boolean)
  428. *
  429. * @return true if the interpretation mode of this calendar is lenient;
  430. * false otherwise.
  431. */
  432. public boolean isLenient() {
  433. return lenient;
  434. }
  435. @Override
  436. public T getValue() {
  437. return value;
  438. }
  439. /**
  440. * Sets the value of this object. If the new value is not equal to
  441. * {@code getValue()}, fires a {@link ValueChangeEvent} .
  442. *
  443. * @param value
  444. * the new value, may be {@code null}
  445. */
  446. @Override
  447. public void setValue(T value) {
  448. /*
  449. * First handle special case when the client side component have a date
  450. * string but value is null (e.g. unparsable date string typed in by the
  451. * user). No value changes should happen, but we need to do some
  452. * internal housekeeping.
  453. */
  454. if (value == null && !uiHasValidDateString) {
  455. /*
  456. * Side-effects of doSetValue clears possible previous strings and
  457. * flags about invalid input.
  458. */
  459. doSetValue(null);
  460. markAsDirty();
  461. return;
  462. }
  463. super.setValue(value);
  464. }
  465. /**
  466. * Checks whether ISO 8601 week numbers are shown in the date selector.
  467. *
  468. * @return true if week numbers are shown, false otherwise.
  469. */
  470. public boolean isShowISOWeekNumbers() {
  471. return showISOWeekNumbers;
  472. }
  473. /**
  474. * Sets the visibility of ISO 8601 week numbers in the date selector. ISO
  475. * 8601 defines that a week always starts with a Monday so the week numbers
  476. * are only shown if this is the case.
  477. *
  478. * @param showWeekNumbers
  479. * true if week numbers should be shown, false otherwise.
  480. */
  481. public void setShowISOWeekNumbers(boolean showWeekNumbers) {
  482. showISOWeekNumbers = showWeekNumbers;
  483. markAsDirty();
  484. }
  485. /**
  486. * Return the error message that is shown if the user inputted value can't
  487. * be parsed into a Date object. If
  488. * {@link #handleUnparsableDateString(String)} is overridden and it throws a
  489. * custom exception, the message returned by
  490. * {@link Exception#getLocalizedMessage()} will be used instead of the value
  491. * returned by this method.
  492. *
  493. * @see #setParseErrorMessage(String)
  494. *
  495. * @return the error message that the DateField uses when it can't parse the
  496. * textual input from user to a Date object
  497. */
  498. public String getParseErrorMessage() {
  499. return defaultParseErrorMessage;
  500. }
  501. /**
  502. * Sets the default error message used if the DateField cannot parse the
  503. * text input by user to a Date field. Note that if the
  504. * {@link #handleUnparsableDateString(String)} method is overridden, the
  505. * localized message from its exception is used.
  506. *
  507. * @see #getParseErrorMessage()
  508. * @see #handleUnparsableDateString(String)
  509. * @param parsingErrorMessage
  510. */
  511. public void setParseErrorMessage(String parsingErrorMessage) {
  512. defaultParseErrorMessage = parsingErrorMessage;
  513. }
  514. @Override
  515. public Registration addFocusListener(FocusListener listener) {
  516. return addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
  517. FocusListener.focusMethod);
  518. }
  519. @Override
  520. public Registration addBlurListener(BlurListener listener) {
  521. return addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
  522. BlurListener.blurMethod);
  523. }
  524. @Override
  525. @SuppressWarnings("unchecked")
  526. public void readDesign(Element design, DesignContext designContext) {
  527. super.readDesign(design, designContext);
  528. if (design.hasAttr("value") && !design.attr("value").isEmpty()) {
  529. Type dateType = GenericTypeReflector.getTypeParameter(getClass(),
  530. AbstractDateField.class.getTypeParameters()[0]);
  531. if (dateType instanceof Class<?>) {
  532. Class<?> clazz = (Class<?>) dateType;
  533. T date = (T) DesignAttributeHandler.getFormatter()
  534. .parse(design.attr("value"), clazz);
  535. // formatting will return null if it cannot parse the string
  536. if (date == null) {
  537. Logger.getLogger(AbstractDateField.class.getName())
  538. .info("cannot parse " + design.attr("value")
  539. + " as date");
  540. }
  541. doSetValue(date);
  542. } else {
  543. throw new RuntimeException("Cannot detect resoluton type "
  544. + Optional.ofNullable(dateType).map(Type::getTypeName)
  545. .orElse(null));
  546. }
  547. }
  548. }
  549. @Override
  550. public void writeDesign(Element design, DesignContext designContext) {
  551. super.writeDesign(design, designContext);
  552. if (getValue() != null) {
  553. design.attr("value",
  554. DesignAttributeHandler.getFormatter().format(getValue()));
  555. }
  556. }
  557. @Override
  558. protected void fireEvent(EventObject event) {
  559. if (event instanceof ValueChangeEvent) {
  560. if (!preventValueChangeEvent) {
  561. super.fireEvent(event);
  562. }
  563. } else {
  564. super.fireEvent(event);
  565. }
  566. }
  567. /**
  568. * This method is called to handle a non-empty date string from the client
  569. * if the client could not parse it as a Date.
  570. *
  571. * By default, an error result is returned whose error message is
  572. * {@link #getParseErrorMessage()}.
  573. *
  574. * This can be overridden to handle conversions, to return a result with
  575. * {@code null} value (equivalent to empty input) or to return a custom
  576. * error.
  577. *
  578. * @param dateString
  579. * date string to handle
  580. * @return result that contains parsed Date as a value or an error
  581. */
  582. protected Result<T> handleUnparsableDateString(String dateString) {
  583. return Result.error(getParseErrorMessage());
  584. }
  585. @Override
  586. protected AbstractDateFieldState getState() {
  587. return (AbstractDateFieldState) super.getState();
  588. }
  589. @Override
  590. protected AbstractDateFieldState getState(boolean markAsDirty) {
  591. return (AbstractDateFieldState) super.getState(markAsDirty);
  592. }
  593. @Override
  594. protected void doSetValue(T value) {
  595. // Also set the internal dateString
  596. if (value != null) {
  597. dateString = value.toString();
  598. } else {
  599. dateString = null;
  600. }
  601. this.value = value;
  602. setComponentError(null);
  603. if (!uiHasValidDateString) {
  604. // clear component error and parsing flag
  605. uiHasValidDateString = true;
  606. setComponentError(new UserError(currentParseErrorMessage));
  607. } else {
  608. RangeValidator<T> validator = getRangeValidator();
  609. ValidationResult result = validator.apply(value,
  610. new ValueContext(this));
  611. if (result.isError()) {
  612. setComponentError(new UserError(getDateOutOfRangeMessage()));
  613. }
  614. }
  615. }
  616. /**
  617. * Returns a date integer value part for the given {@code date} for the
  618. * given {@code resolution}.
  619. *
  620. * @param date
  621. * the given date
  622. * @param resolution
  623. * the resolution to extract a value from the date by
  624. * @return the integer value part of the date by the given resolution
  625. */
  626. protected abstract int getDatePart(T date, R resolution);
  627. /**
  628. * Builds date by the given {@code resolutionValues} which is a map whose
  629. * keys are resolution and integer values.
  630. * <p>
  631. * This is the opposite to {@link #getDatePart(Temporal, Enum)}.
  632. *
  633. * @param resolutionValues
  634. * date values to construct a date
  635. * @return date built from the given map of date values
  636. */
  637. protected abstract T buildDate(Map<R, Integer> resolutionValues);
  638. /**
  639. * Returns a custom date range validator which is applicable for the type
  640. * {@code T}.
  641. *
  642. * @return the date range validator
  643. */
  644. protected abstract RangeValidator<T> getRangeValidator();
  645. /**
  646. * Converts {@link Date} to date type {@code T}.
  647. *
  648. * @param date
  649. * a date to convert
  650. * @return object of type {@code T} representing the {@code date}
  651. */
  652. protected abstract T convertFromDate(Date date);
  653. /**
  654. * Converts the object of type {@code T} to {@link Date}.
  655. * <p>
  656. * This is the opposite to {@link #convertFromDate(Date)}.
  657. *
  658. * @param date
  659. * the date of type {@code T}
  660. * @return converted date of type {@code Date}
  661. */
  662. protected abstract Date convertToDate(T date);
  663. private String getResolutionVariable(R resolution) {
  664. return resolution.name().toLowerCase(Locale.ENGLISH);
  665. }
  666. @SuppressWarnings("unchecked")
  667. private Stream<R> getResolutions() {
  668. Type resolutionType = GenericTypeReflector.getTypeParameter(getClass(),
  669. AbstractDateField.class.getTypeParameters()[1]);
  670. if (resolutionType instanceof Class<?>) {
  671. Class<?> clazz = (Class<?>) resolutionType;
  672. return Stream.of(clazz.getEnumConstants())
  673. .map(object -> (R) object);
  674. } else {
  675. throw new RuntimeException("Cannot detect resoluton type "
  676. + Optional.ofNullable(resolutionType).map(Type::getTypeName)
  677. .orElse(null));
  678. }
  679. }
  680. private Iterable<R> getResolutionsHigherOrEqualTo(R resoution) {
  681. return getResolutions().skip(resolution.ordinal())
  682. .collect(Collectors.toList());
  683. }
  684. }