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 39KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167
  1. /*
  2. * Copyright 2000-2021 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.ZoneId;
  22. import java.time.format.DateTimeFormatter;
  23. import java.time.temporal.Temporal;
  24. import java.time.temporal.TemporalAccessor;
  25. import java.time.temporal.TemporalAdjuster;
  26. import java.util.Calendar;
  27. import java.util.Collections;
  28. import java.util.Date;
  29. import java.util.HashMap;
  30. import java.util.Locale;
  31. import java.util.Map;
  32. import java.util.Map.Entry;
  33. import java.util.Objects;
  34. import java.util.Optional;
  35. import java.util.Set;
  36. import java.util.logging.Logger;
  37. import java.util.stream.Collectors;
  38. import java.util.stream.Stream;
  39. import org.jsoup.nodes.Element;
  40. import com.googlecode.gentyref.GenericTypeReflector;
  41. import com.vaadin.data.Result;
  42. import com.vaadin.data.ValidationResult;
  43. import com.vaadin.data.Validator;
  44. import com.vaadin.data.ValueContext;
  45. import com.vaadin.data.validator.RangeValidator;
  46. import com.vaadin.event.FieldEvents.BlurEvent;
  47. import com.vaadin.event.FieldEvents.BlurListener;
  48. import com.vaadin.event.FieldEvents.BlurNotifier;
  49. import com.vaadin.event.FieldEvents.FocusEvent;
  50. import com.vaadin.event.FieldEvents.FocusListener;
  51. import com.vaadin.event.FieldEvents.FocusNotifier;
  52. import com.vaadin.server.ErrorMessage;
  53. import com.vaadin.server.UserError;
  54. import com.vaadin.shared.Registration;
  55. import com.vaadin.shared.ui.datefield.AbstractDateFieldServerRpc;
  56. import com.vaadin.shared.ui.datefield.AbstractDateFieldState;
  57. import com.vaadin.shared.ui.datefield.AbstractDateFieldState.AccessibleElement;
  58. import com.vaadin.shared.ui.datefield.DateResolution;
  59. import com.vaadin.ui.declarative.DesignAttributeHandler;
  60. import com.vaadin.ui.declarative.DesignContext;
  61. import com.vaadin.util.TimeZoneUtil;
  62. import elemental.json.Json;
  63. /**
  64. * A date editor component with {@link LocalDate} as an input value.
  65. *
  66. * @author Vaadin Ltd
  67. *
  68. * @since 8.0
  69. *
  70. * @param <T>
  71. * type of date ({@code LocalDate} or {@code LocalDateTime}).
  72. * @param <R>
  73. * resolution enumeration type
  74. *
  75. */
  76. public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster & Serializable & Comparable<? super T>, R extends Enum<R>>
  77. extends AbstractField<T> implements FocusNotifier, BlurNotifier {
  78. private static final DateTimeFormatter RANGE_FORMATTER = DateTimeFormatter
  79. .ofPattern("yyyy-MM-dd[ HH:mm:ss]", Locale.ENGLISH);
  80. private AbstractDateFieldServerRpc rpc = new AbstractDateFieldServerRpc() {
  81. @Override
  82. public void update(String newDateString,
  83. Map<String, Integer> resolutions) {
  84. valueUpdate(newDateString, resolutions);
  85. }
  86. @Override
  87. public void updateValueWithDelay(String newDateString,
  88. Map<String, Integer> resolutions) {
  89. valueUpdate(newDateString, resolutions);
  90. }
  91. private void valueUpdate(String newDateString,
  92. Map<String, Integer> resolutions) {
  93. Set<String> resolutionNames = getResolutions().map(Enum::name)
  94. .collect(Collectors.toSet());
  95. resolutionNames.retainAll(resolutions.keySet());
  96. if (!isReadOnly()
  97. && (!resolutionNames.isEmpty() || newDateString != null)) {
  98. // Old and new dates
  99. final T oldDate = getValue();
  100. T newDate;
  101. boolean hasChanges = false;
  102. if ("".equals(newDateString)) {
  103. newDate = null;
  104. } else {
  105. newDate = reconstructDateFromFields(resolutions, oldDate);
  106. }
  107. boolean parseErrorWasSet = currentErrorMessage != null;
  108. hasChanges |= !Objects.equals(dateString, newDateString)
  109. || !Objects.equals(oldDate, newDate)
  110. || parseErrorWasSet;
  111. if (hasChanges) {
  112. dateString = newDateString;
  113. currentErrorMessage = null;
  114. if (newDateString == null || newDateString.isEmpty()) {
  115. boolean valueChanged = setValue(newDate, true);
  116. if (!valueChanged && parseErrorWasSet) {
  117. doSetValue(newDate);
  118. }
  119. } else {
  120. // invalid date string
  121. if (resolutions.isEmpty()) {
  122. Result<T> parsedDate = handleUnparsableDateString(
  123. dateString);
  124. // If handleUnparsableDateString returns the same
  125. // date as current, force update state to display
  126. // correct representation
  127. parsedDate.ifOk(v -> {
  128. if (!setValue(v, true)
  129. && !isDifferentValue(v)) {
  130. updateDiffstate("resolutions",
  131. Json.createObject());
  132. doSetValue(v);
  133. }
  134. });
  135. if (parsedDate.isError()) {
  136. dateString = null;
  137. currentErrorMessage = parsedDate.getMessage()
  138. .orElse("Parsing error");
  139. if (!isDifferentValue(null)) {
  140. doSetValue(null);
  141. } else {
  142. setValue(null, true);
  143. }
  144. }
  145. } else {
  146. RangeValidator<T> validator = getRangeValidator();
  147. ValidationResult result = validator.apply(newDate,
  148. new ValueContext());
  149. if (!isPreventInvalidInput() || !result.isError()) {
  150. setValue(newDate, true);
  151. } else {
  152. doSetValue(newDate);
  153. }
  154. }
  155. }
  156. }
  157. }
  158. }
  159. @Override
  160. public void focus() {
  161. fireEvent(new FocusEvent(AbstractDateField.this));
  162. }
  163. @Override
  164. public void blur() {
  165. fireEvent(new BlurEvent(AbstractDateField.this));
  166. }
  167. };
  168. /**
  169. * The default start year (inclusive) from which to calculate the
  170. * daylight-saving time zone transition dates.
  171. */
  172. private static final int DEFAULT_START_YEAR = 1980;
  173. /**
  174. * The default value of the number of future years from the current date for
  175. * which the daylight-saving time zone transition dates are calculated.
  176. */
  177. private static final int DEFAULT_YEARS_FROM_NOW = 20;
  178. /**
  179. * The optional user-supplied start year (inclusive) from which to calculate
  180. * the daylight-saving time zone transition dates.
  181. */
  182. private Integer startYear;
  183. /**
  184. * The optional user-supplied end year (inclusive) until which to calculate
  185. * the daylight-saving time zone transition dates.
  186. */
  187. private Integer endYear;
  188. /**
  189. * Value of the field.
  190. */
  191. private T value;
  192. /**
  193. * Default value of the field, displayed when nothing has been selected.
  194. *
  195. * @since 8.1.2
  196. */
  197. private T defaultValue;
  198. /**
  199. * Specified smallest modifiable unit for the date field.
  200. */
  201. private R resolution;
  202. private ZoneId zoneId;
  203. private String dateString = "";
  204. private String currentErrorMessage;
  205. private String defaultParseErrorMessage = "Date format not recognized";
  206. private String dateOutOfRangeMessage = "Date is out of allowed range";
  207. private boolean preventInvalidInput = false;
  208. /* Constructors */
  209. /**
  210. * Constructs an empty {@code AbstractDateField} with no caption and
  211. * specified {@code resolution}.
  212. *
  213. * @param resolution
  214. * initial resolution for the field, not {@code null}
  215. */
  216. public AbstractDateField(R resolution) {
  217. registerRpc(rpc);
  218. setResolution(resolution);
  219. }
  220. /**
  221. * Constructs an empty {@code AbstractDateField} with caption.
  222. *
  223. * @param caption
  224. * the caption of the datefield
  225. * @param resolution
  226. * initial resolution for the field, not {@code null}
  227. */
  228. public AbstractDateField(String caption, R resolution) {
  229. this(resolution);
  230. setCaption(caption);
  231. }
  232. /**
  233. * Constructs a new {@code AbstractDateField} with the given caption and
  234. * initial text contents.
  235. *
  236. * @param caption
  237. * the caption {@code String} for the editor.
  238. * @param value
  239. * the date/time value.
  240. * @param resolution
  241. * initial resolution for the field, not {@code null}
  242. */
  243. public AbstractDateField(String caption, T value, R resolution) {
  244. this(caption, resolution);
  245. setValue(value);
  246. }
  247. /* Component basic features */
  248. @Override
  249. public void beforeClientResponse(boolean initial) {
  250. super.beforeClientResponse(initial);
  251. Locale locale = getLocale();
  252. getState().locale = locale == null ? null : locale.toString();
  253. }
  254. /**
  255. * Construct a date object from the individual field values received from
  256. * the client.
  257. *
  258. * @param resolutions
  259. * map of time unit (resolution) name and value, the key is the
  260. * resolution name e.g. "HOUR", "MINUTE", the value can be
  261. * {@code null}
  262. * @param oldDate
  263. * used as a fallback to get needed values if they are not
  264. * defined in the specified {@code resolutions}
  265. *
  266. * @return the date object built from the specified resolutions
  267. * @since 8.2
  268. */
  269. protected T reconstructDateFromFields(Map<String, Integer> resolutions,
  270. T oldDate) {
  271. Map<R, Integer> calendarFields = new HashMap<>();
  272. for (R resolution : getResolutionsHigherOrEqualTo(getResolution())) {
  273. // Only handle what the client is allowed to send. The same
  274. // resolutions that are painted
  275. String resolutionName = resolution.name();
  276. Integer newValue = resolutions.get(resolutionName);
  277. if (newValue == null) {
  278. newValue = getDatePart(oldDate, resolution);
  279. }
  280. calendarFields.put(resolution, newValue);
  281. }
  282. return buildDate(calendarFields);
  283. }
  284. /**
  285. * Sets the start range for this component. If the value is set before this
  286. * date (taking the resolution into account), the component will not
  287. * validate. If {@code startDate} is set to {@code null}, any value before
  288. * {@code endDate} will be accepted by the range
  289. * <p>
  290. * Note: Negative, i.e. BC dates are not supported.
  291. * <p>
  292. * Note: It's usually recommended to use only one of the following at the
  293. * same time: Range validator with Binder or DateField's setRangeStart
  294. * check.
  295. *
  296. * @param startDate
  297. * - the allowed range's start date
  298. */
  299. public void setRangeStart(T startDate) {
  300. if (afterDate(startDate, convertFromDateString(getState().rangeEnd))) {
  301. throw new IllegalStateException(
  302. "startDate cannot be later than endDate");
  303. }
  304. getState().rangeStart = convertToDateString(startDate);
  305. }
  306. /**
  307. * Sets the current error message if the range validation fails.
  308. *
  309. * @param dateOutOfRangeMessage
  310. * - Localizable message which is shown when value (the date) is
  311. * set outside allowed range
  312. */
  313. public void setDateOutOfRangeMessage(String dateOutOfRangeMessage) {
  314. this.dateOutOfRangeMessage = dateOutOfRangeMessage;
  315. }
  316. /**
  317. * Returns current date-out-of-range error message.
  318. *
  319. * @see #setDateOutOfRangeMessage(String)
  320. * @return Current error message for dates out of range.
  321. */
  322. public String getDateOutOfRangeMessage() {
  323. return dateOutOfRangeMessage;
  324. }
  325. /**
  326. * Gets the resolution.
  327. *
  328. * @return the date/time field resolution
  329. */
  330. public R getResolution() {
  331. return resolution;
  332. }
  333. /**
  334. * Sets the resolution of the DateField.
  335. *
  336. * The default resolution is {@link DateResolution#DAY} since Vaadin 7.0.
  337. *
  338. * @param resolution
  339. * the resolution to set, not {@code null}
  340. */
  341. public void setResolution(R resolution) {
  342. if (!resolution.equals(this.resolution)) {
  343. this.resolution = resolution;
  344. setValue(adjustToResolution(getValue(), resolution));
  345. updateResolutions();
  346. }
  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} is set to {@code null}, any value after
  352. * {@code startDate} will be accepted by the range.
  353. * <p>
  354. * Note: It's usually recommended to use only one of the following at the
  355. * same time: Range validator with Binder or DateField's setRangeEnd check.
  356. *
  357. * @param endDate
  358. * the allowed range's end date (inclusive, based on the current
  359. * resolution)
  360. */
  361. public void setRangeEnd(T endDate) {
  362. String date = convertToDateString(endDate);
  363. if (afterDate(convertFromDateString(getState().rangeStart), endDate)) {
  364. throw new IllegalStateException(
  365. "endDate cannot be earlier than startDate");
  366. }
  367. getState().rangeEnd = date;
  368. }
  369. /**
  370. * Returns the precise rangeStart used.
  371. *
  372. * @return the precise rangeStart used, may be {@code null}.
  373. */
  374. public T getRangeStart() {
  375. return convertFromDateString(getState(false).rangeStart);
  376. }
  377. /**
  378. * Parses string representation of date range limit into date type.
  379. *
  380. * @param temporalStr
  381. * the string representation
  382. * @return parsed value
  383. * @see AbstractDateFieldState#rangeStart
  384. * @see AbstractDateFieldState#rangeEnd
  385. * @since 8.4
  386. */
  387. protected T convertFromDateString(String temporalStr) {
  388. if (temporalStr == null) {
  389. return null;
  390. }
  391. return toType(RANGE_FORMATTER.parse(temporalStr));
  392. }
  393. /**
  394. * Converts a temporal value into field-specific data type.
  395. *
  396. * @param temporalAccessor
  397. * - source value
  398. * @return conversion result.
  399. * @since 8.4
  400. */
  401. protected abstract T toType(TemporalAccessor temporalAccessor);
  402. /**
  403. * Converts date range limit into string representation.
  404. *
  405. * @param temporal
  406. * the value
  407. * @return textual representation
  408. * @see AbstractDateFieldState#rangeStart
  409. * @see AbstractDateFieldState#rangeEnd
  410. * @since 8.4
  411. */
  412. protected String convertToDateString(T temporal) {
  413. if (temporal == null) {
  414. return null;
  415. }
  416. return RANGE_FORMATTER.format(temporal);
  417. }
  418. /**
  419. * Checks if {@code value} is after {@code base} or not.
  420. *
  421. * @param value
  422. * temporal value
  423. * @param base
  424. * temporal value to compare to
  425. * @return {@code true} if {@code value} is after {@code base},
  426. * {@code false} otherwise
  427. */
  428. protected boolean afterDate(T value, T base) {
  429. if (value == null || base == null) {
  430. return false;
  431. }
  432. return value.compareTo(base) > 0;
  433. }
  434. /**
  435. * Returns the precise rangeEnd used.
  436. *
  437. * @return the precise rangeEnd used, may be {@code null}.
  438. */
  439. public T getRangeEnd() {
  440. return convertFromDateString(getState(false).rangeEnd);
  441. }
  442. /**
  443. * Sets formatting used by some component implementations. See
  444. * {@link SimpleDateFormat} for format details.
  445. *
  446. * By default it is encouraged to used default formatting defined by Locale,
  447. * but due some JVM bugs it is sometimes necessary to use this method to
  448. * override formatting. See Vaadin issue #2200.
  449. *
  450. * @param dateFormat
  451. * the dateFormat to set, can be {@code null}
  452. *
  453. * @see com.vaadin.ui.AbstractComponent#setLocale(Locale))
  454. */
  455. public void setDateFormat(String dateFormat) {
  456. getState().format = dateFormat;
  457. }
  458. /**
  459. * Returns a format string used to format date value on client side or
  460. * {@code null} if default formatting from {@link Component#getLocale()} is
  461. * used.
  462. *
  463. * @return the dateFormat
  464. */
  465. public String getDateFormat() {
  466. return getState(false).format;
  467. }
  468. /**
  469. * Sets the {@link ZoneId}, which is used when {@code z} is included inside
  470. * the {@link #setDateFormat(String)} .
  471. *
  472. * @param zoneId
  473. * the zone id
  474. * @since 8.2
  475. */
  476. public void setZoneId(ZoneId zoneId) {
  477. if (zoneId != this.zoneId
  478. || (zoneId != null && !zoneId.equals(this.zoneId))) {
  479. updateTimeZoneJSON(zoneId, getLocale(), getStartYear(),
  480. getEndYear());
  481. }
  482. this.zoneId = zoneId;
  483. }
  484. private void updateTimeZoneJSON(ZoneId zoneId, Locale locale, int startYear,
  485. int endYear) {
  486. String timeZoneJSON;
  487. if (zoneId != null && locale != null) {
  488. timeZoneJSON = TimeZoneUtil.toJSON(zoneId, locale, startYear,
  489. endYear);
  490. } else {
  491. timeZoneJSON = null;
  492. }
  493. getState().timeZoneJSON = timeZoneJSON;
  494. }
  495. /**
  496. * Sets {@link startYear} and {@link endYear}: the start and end years (both
  497. * inclusive) between which to calculate the daylight-saving time zone
  498. * transition dates. Both parameters are used when '{@code z}' is included
  499. * inside the {@link #setDateFormat(String)}, they would have no effect
  500. * otherwise. Specifically, these parameters determine the range of years in
  501. * which zone names are are adjusted to show the daylight saving names.
  502. *
  503. * If no values are provided, by default {@link startYear} is set to
  504. * {@value #DEFAULT_START_YEAR}, and {@link endYear} is set to
  505. * {@value #DEFAULT_YEARS_FROM_NOW} years into the future from the current
  506. * date.
  507. *
  508. * @param startYear
  509. * the start year of DST transitions
  510. * @param endYear
  511. * the end year of DST transitions
  512. * @since 8.11
  513. */
  514. public void setDaylightSavingTimeRange(int startYear, int endYear) {
  515. if (startYear > endYear) {
  516. throw new IllegalArgumentException(
  517. "The start year from which to begin calculating the "
  518. + "daylight-saving time zone transition dates must"
  519. + " be less than or equal to the end year.\n"
  520. + startYear + " is greater than " + endYear);
  521. }
  522. if (this.startYear == null || this.endYear == null
  523. || startYear != this.startYear || endYear != this.endYear) {
  524. updateTimeZoneJSON(getZoneId(), getLocale(), startYear, endYear);
  525. }
  526. this.startYear = startYear;
  527. this.endYear = endYear;
  528. }
  529. private int getStartYear() {
  530. if (startYear == null) {
  531. return DEFAULT_START_YEAR;
  532. } else {
  533. return startYear;
  534. }
  535. }
  536. private int getEndYear() {
  537. if (endYear == null) {
  538. return LocalDate.now().getYear() + DEFAULT_YEARS_FROM_NOW;
  539. } else {
  540. return endYear;
  541. }
  542. }
  543. @Override
  544. public void setLocale(Locale locale) {
  545. Locale oldLocale = getLocale();
  546. if (locale != oldLocale
  547. || (locale != null && !locale.equals(oldLocale))) {
  548. updateTimeZoneJSON(getZoneId(), locale, getStartYear(),
  549. getEndYear());
  550. }
  551. super.setLocale(locale);
  552. }
  553. private void updateResolutions() {
  554. final T currentDate = getValue();
  555. Map<String, Integer> resolutions = getState().resolutions;
  556. resolutions.clear();
  557. // Only paint variables for the resolution and up, e.g. Resolution DAY
  558. // paints DAY,MONTH,YEAR
  559. for (R resolution : getResolutionsHigherOrEqualTo(getResolution())) {
  560. String resolutionName = resolution.name();
  561. Integer value = getValuePart(currentDate, resolution);
  562. resolutions.put(resolutionName, value);
  563. Integer defaultValuePart = getValuePart(defaultValue, resolution);
  564. resolutions.put("default-" + resolutionName, defaultValuePart);
  565. }
  566. updateDiffstate("resolutions", Json.createObject());
  567. }
  568. private Integer getValuePart(T date, R resolution) {
  569. if (date == null) {
  570. return null;
  571. }
  572. return getDatePart(date, resolution);
  573. }
  574. /**
  575. * Returns the {@link ZoneId}, which is used when {@code z} is included
  576. * inside the {@link #setDateFormat(String)}.
  577. *
  578. * @return the zoneId
  579. * @since 8.2
  580. */
  581. public ZoneId getZoneId() {
  582. return zoneId;
  583. }
  584. /**
  585. * Specifies whether or not date/time interpretation in component is to be
  586. * lenient.
  587. *
  588. * @see Calendar#setLenient(boolean)
  589. * @see #isLenient()
  590. *
  591. * @param lenient
  592. * true if the lenient mode is to be turned on; false if it is to
  593. * be turned off.
  594. */
  595. public void setLenient(boolean lenient) {
  596. getState().lenient = lenient;
  597. }
  598. /**
  599. * Returns whether date/time interpretation is lenient.
  600. *
  601. * @see #setLenient(boolean)
  602. *
  603. * @return {@code true} if the interpretation mode of this calendar is
  604. * lenient; {@code false} otherwise.
  605. */
  606. public boolean isLenient() {
  607. return getState(false).lenient;
  608. }
  609. @Override
  610. public T getValue() {
  611. return value;
  612. }
  613. /**
  614. * Returns the current default value.
  615. *
  616. * @see #setDefaultValue(Temporal)
  617. * @return the default value
  618. * @since 8.1.2
  619. */
  620. public T getDefaultValue() {
  621. return defaultValue;
  622. }
  623. /**
  624. * Sets the default value for the field. The default value is the starting
  625. * point for the date field when nothing has been selected yet. If no
  626. * default value is set, current date/time is used.
  627. *
  628. * @param defaultValue
  629. * the default value, may be {@code null}
  630. * @since 8.1.2
  631. */
  632. public void setDefaultValue(T defaultValue) {
  633. this.defaultValue = defaultValue;
  634. updateResolutions();
  635. }
  636. /**
  637. * Sets the value of this object. If the new value is not equal to
  638. * {@code getValue()}, fires a {@link ValueChangeEvent} .
  639. *
  640. * @param value
  641. * the new value, may be {@code null}
  642. * @throws IllegalArgumentException
  643. * if the value is not within range bounds
  644. */
  645. @Override
  646. public void setValue(T value) {
  647. T adjusted = adjustToResolution(value, getResolution());
  648. RangeValidator<T> validator = getRangeValidator();
  649. ValidationResult result = validator.apply(adjusted,
  650. new ValueContext(this, this));
  651. if (result.isError()) {
  652. throw new IllegalArgumentException(
  653. "value is not within acceptable range");
  654. } else {
  655. currentErrorMessage = null;
  656. /*
  657. * First handle special case when the client side component has a
  658. * date string but value is null (e.g. unparsable date string typed
  659. * in by the user). No value changes should happen, but we need to
  660. * do some internal housekeeping.
  661. */
  662. if (adjusted == null && !getState(false).parsable) {
  663. /*
  664. * Side-effects of doSetValue clears possible previous strings
  665. * and flags about invalid input.
  666. */
  667. doSetValue(null);
  668. markAsDirty();
  669. return;
  670. }
  671. super.setValue(adjusted);
  672. }
  673. }
  674. /**
  675. * Adjusts the given date to the given resolution. Any values that are more
  676. * specific than the given resolution are truncated to their default values.
  677. *
  678. * @param date
  679. * the date to adjust, can be {@code null}
  680. * @param resolution
  681. * the resolution to be used in the adjustment, can be
  682. * {@code null}
  683. * @return an adjusted date that matches the given resolution, or
  684. * {@code null} if the given date, resolution, or both were
  685. * {@code null}
  686. */
  687. protected abstract T adjustToResolution(T date, R resolution);
  688. /**
  689. * Checks whether ISO 8601 week numbers are shown in the date selector.
  690. *
  691. * @return true if week numbers are shown, false otherwise.
  692. */
  693. public boolean isShowISOWeekNumbers() {
  694. return getState(false).showISOWeekNumbers;
  695. }
  696. /**
  697. * Sets the visibility of ISO 8601 week numbers in the date selector. ISO
  698. * 8601 defines that a week always starts with a Monday so the week numbers
  699. * are only shown if this is the case.
  700. *
  701. * @param showWeekNumbers
  702. * true if week numbers should be shown, false otherwise.
  703. */
  704. public void setShowISOWeekNumbers(boolean showWeekNumbers) {
  705. getState().showISOWeekNumbers = showWeekNumbers;
  706. }
  707. /**
  708. * Return the error message that is shown if the user inputted value can't
  709. * be parsed into a Date object. If
  710. * {@link #handleUnparsableDateString(String)} is overridden and it throws a
  711. * custom exception, the message returned by
  712. * {@link Exception#getLocalizedMessage()} will be used instead of the value
  713. * returned by this method.
  714. *
  715. * @see #setParseErrorMessage(String)
  716. *
  717. * @return the error message that the DateField uses when it can't parse the
  718. * textual input from user to a Date object
  719. */
  720. public String getParseErrorMessage() {
  721. return defaultParseErrorMessage;
  722. }
  723. /**
  724. * Sets the default error message used if the DateField cannot parse the
  725. * text input by user to a Date field. Note that if the
  726. * {@link #handleUnparsableDateString(String)} method is overridden, the
  727. * localized message from its exception is used.
  728. *
  729. * @param parsingErrorMessage
  730. * the default parsing error message
  731. *
  732. * @see #getParseErrorMessage()
  733. * @see #handleUnparsableDateString(String)
  734. */
  735. public void setParseErrorMessage(String parsingErrorMessage) {
  736. defaultParseErrorMessage = parsingErrorMessage;
  737. }
  738. @Override
  739. public Registration addFocusListener(FocusListener listener) {
  740. return addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
  741. FocusListener.focusMethod);
  742. }
  743. @Override
  744. public Registration addBlurListener(BlurListener listener) {
  745. return addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
  746. BlurListener.blurMethod);
  747. }
  748. @Override
  749. @SuppressWarnings("unchecked")
  750. public void readDesign(Element design, DesignContext designContext) {
  751. super.readDesign(design, designContext);
  752. if (design.hasAttr("value") && !design.attr("value").isEmpty()) {
  753. Type dateType = GenericTypeReflector.getTypeParameter(getClass(),
  754. AbstractDateField.class.getTypeParameters()[0]);
  755. if (dateType instanceof Class<?>) {
  756. Class<?> clazz = (Class<?>) dateType;
  757. T date = (T) DesignAttributeHandler.getFormatter()
  758. .parse(design.attr("value"), clazz);
  759. // formatting will return null if it cannot parse the string
  760. if (date == null) {
  761. Logger.getLogger(AbstractDateField.class.getName())
  762. .info("cannot parse " + design.attr("value")
  763. + " as date");
  764. }
  765. doSetValue(adjustToResolution(date, getResolution()));
  766. } else {
  767. throw new RuntimeException("Cannot detect resoluton type "
  768. + Optional.ofNullable(dateType).map(Type::getTypeName)
  769. .orElse(null));
  770. }
  771. }
  772. }
  773. /**
  774. * Formats date according to the components locale.
  775. *
  776. * @param value
  777. * the date or {@code null}
  778. * @return textual representation of the date or empty string for
  779. * {@code null}
  780. * @since 8.1.1
  781. */
  782. protected abstract String formatDate(T value);
  783. @Override
  784. public void writeDesign(Element design, DesignContext designContext) {
  785. super.writeDesign(design, designContext);
  786. if (getValue() != null) {
  787. design.attr("value",
  788. DesignAttributeHandler.getFormatter().format(getValue()));
  789. }
  790. }
  791. /**
  792. * This method is called to handle a non-empty date string from the client
  793. * if the client could not parse it as a Date.
  794. *
  795. * By default, an error result is returned whose error message is
  796. * {@link #getParseErrorMessage()}.
  797. *
  798. * This can be overridden to handle conversions, to return a result with
  799. * {@code null} value (equivalent to empty input) or to return a custom
  800. * error.
  801. *
  802. * @param dateString
  803. * date string to handle
  804. * @return result that contains parsed Date as a value or an error
  805. */
  806. protected Result<T> handleUnparsableDateString(String dateString) {
  807. return Result.error(getParseErrorMessage());
  808. }
  809. @Override
  810. protected AbstractDateFieldState getState() {
  811. return (AbstractDateFieldState) super.getState();
  812. }
  813. @Override
  814. protected AbstractDateFieldState getState(boolean markAsDirty) {
  815. return (AbstractDateFieldState) super.getState(markAsDirty);
  816. }
  817. @Override
  818. protected void doSetValue(T value) {
  819. // Also set the internal dateString
  820. this.value = value;
  821. if (value == null) {
  822. value = getEmptyValue();
  823. }
  824. dateString = formatDate(value);
  825. // TODO move range check to internal validator?
  826. RangeValidator<T> validator = getRangeValidator();
  827. ValidationResult result = validator.apply(value,
  828. new ValueContext(this, this));
  829. if (result.isError()) {
  830. currentErrorMessage = getDateOutOfRangeMessage();
  831. }
  832. getState().parsable = currentErrorMessage == null;
  833. ErrorMessage errorMessage;
  834. if (currentErrorMessage == null) {
  835. errorMessage = null;
  836. } else {
  837. errorMessage = new UserError(currentErrorMessage);
  838. }
  839. setComponentError(errorMessage);
  840. updateResolutions();
  841. }
  842. /**
  843. * Returns a date integer value part for the given {@code date} for the
  844. * given {@code resolution}.
  845. *
  846. * @param date
  847. * the given date, can be {@code null}
  848. * @param resolution
  849. * the resolution to extract a value from the date by, not
  850. * {@code null}
  851. * @return the integer value part of the date by the given resolution
  852. */
  853. protected abstract int getDatePart(T date, R resolution);
  854. /**
  855. * Builds date by the given {@code resolutionValues} which is a map whose
  856. * keys are resolution and integer values.
  857. * <p>
  858. * This is the opposite to {@link #getDatePart(Temporal, Enum)}.
  859. *
  860. * @param resolutionValues
  861. * date values to construct a date
  862. * @return date built from the given map of date values
  863. */
  864. protected abstract T buildDate(Map<R, Integer> resolutionValues);
  865. /**
  866. * Returns a custom date range validator which is applicable for the type
  867. * {@code T}.
  868. *
  869. * @return the date range validator
  870. */
  871. protected abstract RangeValidator<T> getRangeValidator();
  872. /**
  873. * Converts {@link Date} to date type {@code T}.
  874. *
  875. * @param date
  876. * a date to convert
  877. * @return object of type {@code T} representing the {@code date}
  878. */
  879. protected abstract T convertFromDate(Date date);
  880. /**
  881. * Converts the object of type {@code T} to {@link Date}.
  882. * <p>
  883. * This is the opposite to {@link #convertFromDate(Date)}.
  884. *
  885. * @param date
  886. * the date of type {@code T}
  887. * @return converted date of type {@code Date}
  888. */
  889. protected abstract Date convertToDate(T date);
  890. @SuppressWarnings("unchecked")
  891. private Stream<R> getResolutions() {
  892. Type resolutionType = GenericTypeReflector.getTypeParameter(getClass(),
  893. AbstractDateField.class.getTypeParameters()[1]);
  894. if (resolutionType instanceof Class<?>) {
  895. Class<?> clazz = (Class<?>) resolutionType;
  896. return Stream.of(clazz.getEnumConstants())
  897. .map(object -> (R) object);
  898. }
  899. throw new RuntimeException("Cannot detect resoluton type "
  900. + Optional.ofNullable(resolutionType).map(Type::getTypeName)
  901. .orElse(null));
  902. }
  903. private Iterable<R> getResolutionsHigherOrEqualTo(R resolution) {
  904. return getResolutions().skip(resolution.ordinal())
  905. .collect(Collectors.toList());
  906. }
  907. @Override
  908. public Validator<T> getDefaultValidator() {
  909. return new Validator<T>() {
  910. @Override
  911. public ValidationResult apply(T value, ValueContext context) {
  912. // currentErrorMessage contains two type of messages, one is
  913. // DateOutOfRangeMessage and the other one is the ParseError
  914. if (currentErrorMessage != null) {
  915. if (currentErrorMessage
  916. .equals(getDateOutOfRangeMessage())) {
  917. // if the currentErrorMessage is DateOutOfRangeMessage,
  918. // then need to double check whether the error message
  919. // has been updated, that is because of #11276.
  920. ValidationResult validationResult = getRangeValidator()
  921. .apply(value, context);
  922. if (validationResult.isError()) {
  923. return ValidationResult.error(currentErrorMessage);
  924. }
  925. } else {
  926. // if the current Error is parsing error, pass it to the
  927. // ValidationResult
  928. return ValidationResult.error(currentErrorMessage);
  929. }
  930. }
  931. // Pass to range validator.
  932. return getRangeValidator().apply(value, context);
  933. }
  934. };
  935. }
  936. /**
  937. * <p>
  938. * Sets a custom style name for the given date's calendar cell. Setting the
  939. * style name will override any previous style names that have been set for
  940. * that date, but can contain several actual style names separated by space.
  941. * Setting the custom style name {@code null} will only remove the previous
  942. * custom style name.
  943. * </p>
  944. * <p>
  945. * This logic is entirely separate from {@link #setStyleName(String)}
  946. * </p>
  947. * <p>
  948. * Usage examples: <br>
  949. * {@code setDateStyle(LocalDate.now(), "teststyle");} <br>
  950. * {@code setDateStyle(LocalDate.now(), "teststyle1 teststyle2");}
  951. * </p>
  952. *
  953. * @param date
  954. * which date cell to modify, not {@code null}
  955. * @param styleName
  956. * the custom style name(s) for given date, {@code null} to clear
  957. * custom style name(s)
  958. *
  959. * @since 8.3
  960. */
  961. public void setDateStyle(LocalDate date, String styleName) {
  962. Objects.requireNonNull(date, "Date cannot be null");
  963. if (styleName != null) {
  964. getState().dateStyles.put(date.toString(), styleName);
  965. } else {
  966. getState().dateStyles.remove(date.toString());
  967. }
  968. }
  969. /**
  970. * Returns the custom style name that corresponds with the given date's
  971. * calendar cell.
  972. *
  973. * @param date
  974. * which date cell's custom style name(s) to return, not
  975. * {@code null}
  976. * @return the corresponding style name(s), if any, {@code null} otherwise
  977. *
  978. * @see #setDateStyle(LocalDate, String)
  979. * @since 8.3
  980. */
  981. public String getDateStyle(LocalDate date) {
  982. Objects.requireNonNull(date, "Date cannot be null");
  983. return getState(false).dateStyles.get(date.toString());
  984. }
  985. /**
  986. * Returns a map from dates to custom style names in each date's calendar
  987. * cell.
  988. *
  989. * @return unmodifiable map from dates to custom style names in each date's
  990. * calendar cell
  991. *
  992. * @see #setDateStyle(LocalDate, String)
  993. * @since 8.3
  994. */
  995. public Map<LocalDate, String> getDateStyles() {
  996. HashMap<LocalDate, String> hashMap = new HashMap<>();
  997. for (Entry<String, String> entry : getState(false).dateStyles
  998. .entrySet()) {
  999. hashMap.put(LocalDate.parse(entry.getKey()), entry.getValue());
  1000. }
  1001. return Collections.unmodifiableMap(hashMap);
  1002. }
  1003. /**
  1004. * Sets the assistive label for a calendar navigation element. This sets the
  1005. * {@code aria-label} attribute for the element which is used by screen
  1006. * reading software.
  1007. *
  1008. * @param element
  1009. * the element for which to set the label. Not {@code null}.
  1010. * @param label
  1011. * the assistive label to set
  1012. * @since 8.4
  1013. */
  1014. public void setAssistiveLabel(AccessibleElement element, String label) {
  1015. Objects.requireNonNull(element, "Element cannot be null");
  1016. getState().assistiveLabels.put(element, label);
  1017. }
  1018. /**
  1019. * Gets the assistive label of a calendar navigation element.
  1020. *
  1021. * @param element
  1022. * the element of which to get the assistive label
  1023. * @since 8.4
  1024. */
  1025. public void getAssistiveLabel(AccessibleElement element) {
  1026. getState(false).assistiveLabels.get(element);
  1027. }
  1028. /**
  1029. * Control whether value change event is emitted when user input value does
  1030. * not meet the integrated range validator.
  1031. *
  1032. * @param preventInvalidInput
  1033. * Set to false to disable the value change event.
  1034. *
  1035. * @since 8.13
  1036. */
  1037. public void setPreventInvalidInput(boolean preventInvalidInput) {
  1038. this.preventInvalidInput = preventInvalidInput;
  1039. }
  1040. /**
  1041. * Check whether value change is emitted when user input value does not meet
  1042. * integrated range validator. The default is false.
  1043. *
  1044. * @return a Boolean value
  1045. *
  1046. * @since 8.13
  1047. */
  1048. public boolean isPreventInvalidInput() {
  1049. return preventInvalidInput;
  1050. }
  1051. }