001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.acacia.client.widgets.serialdate; 029 030import org.opencms.acacia.shared.A_CmsSerialDateValue; 031import org.opencms.acacia.shared.I_CmsSerialDateValue; 032import org.opencms.gwt.client.util.CmsDebugLog; 033import org.opencms.util.CmsUUID; 034 035import java.util.Collection; 036import java.util.Date; 037import java.util.HashSet; 038import java.util.SortedSet; 039import java.util.TreeSet; 040 041import com.google.gwt.json.client.JSONArray; 042import com.google.gwt.json.client.JSONBoolean; 043import com.google.gwt.json.client.JSONObject; 044import com.google.gwt.json.client.JSONParser; 045import com.google.gwt.json.client.JSONString; 046import com.google.gwt.json.client.JSONValue; 047 048/** 049 * Client-side implementation of {@link I_CmsSerialDateValue}. 050 * The implementation additionally has setters for the various values of the serial date specification. 051 */ 052public class CmsSerialDateValue extends A_CmsSerialDateValue implements I_CmsObservableSerialDateValue { 053 054 /** The list of value change observers. */ 055 Collection<I_CmsSerialDateValueChangeObserver> m_valueChangeObservers = new HashSet<>(); 056 057 /** Default constructor, setting the default state of the the serial date widget. */ 058 public CmsSerialDateValue() { 059 060 setDefaultValue(); 061 } 062 063 /** 064 * @see org.opencms.acacia.client.widgets.serialdate.I_CmsObservableSerialDateValue#registerValueChangeObserver(org.opencms.acacia.client.widgets.serialdate.I_CmsSerialDateValueChangeObserver) 065 */ 066 public void registerValueChangeObserver(I_CmsSerialDateValueChangeObserver obs) { 067 068 m_valueChangeObservers.add(obs); 069 070 } 071 072 /** 073 * Set the value as provided. 074 * @param value the serial date value as JSON string. 075 */ 076 public final void setValue(String value) { 077 078 if ((null == value) || value.isEmpty()) { 079 setDefaultValue(); 080 } else { 081 try { 082 tryToSetParsedValue(value); 083 } catch (@SuppressWarnings("unused") Exception e) { 084 CmsDebugLog.consoleLog("Could not set invalid serial date value: " + value); 085 setDefaultValue(); 086 } 087 } 088 notifyOnValueChange(); 089 } 090 091 /** 092 * Convert the information from the wrapper to a JSON object. 093 * @return the serial date information as JSON. 094 */ 095 public JSONValue toJson() { 096 097 JSONObject result = new JSONObject(); 098 if (null != getStart()) { 099 result.put(JsonKey.START, dateToJson(getStart())); 100 } 101 if (null != getEnd()) { 102 result.put(JsonKey.END, dateToJson(getEnd())); 103 } 104 if (isWholeDay()) { 105 result.put(JsonKey.WHOLE_DAY, JSONBoolean.getInstance(true)); 106 } 107 JSONObject pattern = patternToJson(); 108 result.put(JsonKey.PATTERN, pattern); 109 SortedSet<Date> exceptions = getExceptions(); 110 if (exceptions.size() > 0) { 111 result.put(JsonKey.EXCEPTIONS, datesToJsonArray(exceptions)); 112 } 113 switch (getEndType()) { 114 case DATE: 115 result.put(JsonKey.SERIES_ENDDATE, dateToJson(getSeriesEndDate())); 116 break; 117 case TIMES: 118 result.put(JsonKey.SERIES_OCCURRENCES, new JSONString(String.valueOf(getOccurrences()))); 119 break; 120 case SINGLE: 121 default: 122 break; 123 } 124 if (!isCurrentTillEnd()) { 125 result.put(JsonKey.CURRENT_TILL_END, JSONBoolean.getInstance(false)); 126 } 127 if (getParentSeriesId() != null) { 128 result.put(JsonKey.PARENT_SERIES, new JSONString(getParentSeriesId().getStringValue())); 129 } 130 return result; 131 } 132 133 /** 134 * @see java.lang.Object#toString() 135 */ 136 @Override 137 public String toString() { 138 139 JSONValue json = toJson(); 140 return json.toString(); 141 } 142 143 /** 144 * @see org.opencms.acacia.client.widgets.serialdate.I_CmsObservableSerialDateValue#unregisterValueChangeObserver(org.opencms.acacia.client.widgets.serialdate.I_CmsSerialDateValueChangeObserver) 145 */ 146 public void unregisterValueChangeObserver(I_CmsSerialDateValueChangeObserver obs) { 147 148 m_valueChangeObservers.remove(obs); 149 150 } 151 152 /** 153 * Converts a collection of dates to a JSON array with the long representation of the dates as strings. 154 * @param dates the list to convert. 155 * @return JSON array with long values of dates as string 156 */ 157 private JSONValue datesToJsonArray(Collection<Date> dates) { 158 159 if (null != dates) { 160 JSONArray result = new JSONArray(); 161 for (Date d : dates) { 162 result.set(result.size(), dateToJson(d)); 163 } 164 return result; 165 } 166 return null; 167 } 168 169 /** 170 * Convert a date to the String representation we use in the JSON. 171 * @param d the date to convert 172 * @return the String representation we use in the JSON. 173 */ 174 private JSONValue dateToJson(Date d) { 175 176 return null != d ? new JSONString(Long.toString(d.getTime())) : null; 177 } 178 179 /** 180 * Notifies all observers on value changes. 181 */ 182 private void notifyOnValueChange() { 183 184 for (I_CmsSerialDateValueChangeObserver obs : m_valueChangeObservers) { 185 obs.onValueChange(); 186 } 187 188 } 189 190 /** 191 * Generates the JSON object storing the pattern information. 192 * @return the JSON object storing the pattern information. 193 */ 194 private JSONObject patternToJson() { 195 196 JSONObject pattern = new JSONObject(); 197 if (null != getPatternType()) { 198 pattern.put(JsonKey.PATTERN_TYPE, new JSONString(getPatternType().toString())); 199 switch (getPatternType()) { 200 case DAILY: 201 if (isEveryWorkingDay()) { 202 pattern.put(JsonKey.PATTERN_EVERYWORKINGDAY, JSONBoolean.getInstance(true)); 203 } else { 204 pattern.put(JsonKey.PATTERN_INTERVAL, new JSONString(String.valueOf(getInterval()))); 205 } 206 break; 207 case WEEKLY: 208 pattern.put(JsonKey.PATTERN_INTERVAL, new JSONString(String.valueOf(getInterval()))); 209 pattern.put(JsonKey.PATTERN_WEEKDAYS, toJsonStringList(getWeekDays())); 210 break; 211 case MONTHLY: 212 pattern.put(JsonKey.PATTERN_INTERVAL, new JSONString(String.valueOf(getInterval()))); 213 if (null != getWeekDay()) { 214 pattern.put(JsonKey.PATTERN_WEEKS_OF_MONTH, toJsonStringList(getWeeksOfMonth())); 215 pattern.put(JsonKey.PATTERN_WEEKDAYS, toJsonStringList(getWeekDays())); 216 } else { 217 pattern.put(JsonKey.PATTERN_DAY_OF_MONTH, new JSONString(String.valueOf(getDayOfMonth()))); 218 } 219 break; 220 case YEARLY: 221 pattern.put(JsonKey.PATTERN_MONTH, new JSONString(String.valueOf(getMonth()))); 222 if (null != getWeekDay()) { 223 pattern.put(JsonKey.PATTERN_WEEKS_OF_MONTH, toJsonStringList(getWeeksOfMonth())); 224 pattern.put(JsonKey.PATTERN_WEEKDAYS, toJsonStringList(getWeekDays())); 225 } else { 226 pattern.put(JsonKey.PATTERN_DAY_OF_MONTH, new JSONString(String.valueOf(getDayOfMonth()))); 227 } 228 break; 229 case INDIVIDUAL: 230 pattern.put(JsonKey.PATTERN_DATES, datesToJsonArray(getIndividualDates())); 231 break; 232 case NONE: 233 default: 234 break; 235 } 236 } 237 return pattern; 238 } 239 240 /** 241 * Extracts the dates from a JSON array. 242 * @param json the JSON array where the dates are stored in. 243 * @return list of the extracted dates. 244 */ 245 private SortedSet<Date> readDates(JSONValue json) { 246 247 JSONArray array = null == json ? null : json.isArray(); 248 if (null != array) { 249 SortedSet<Date> result = new TreeSet<>(); 250 for (int i = 0; i < array.size(); i++) { 251 Date d = readOptionalDate(array.get(i)); 252 if (null != d) { 253 result.add(d); 254 } 255 } 256 return result; 257 } 258 return new TreeSet<>(); 259 } 260 261 /** 262 * Read an optional boolean value form a JSON value. 263 * @param val the JSON value that should represent the boolean. 264 * @return the boolean from the JSON or null if reading the boolean fails. 265 */ 266 private Boolean readOptionalBoolean(JSONValue val) { 267 268 JSONBoolean b = null == val ? null : val.isBoolean(); 269 if (b != null) { 270 return Boolean.valueOf(b.booleanValue()); 271 } 272 return null; 273 } 274 275 /** 276 * Read an optional Date value form a JSON value. 277 * @param val the JSON value that should represent the Date as long value in a string. 278 * @return the Date from the JSON or null if reading the date fails. 279 */ 280 private Date readOptionalDate(JSONValue val) { 281 282 JSONString str = null == val ? null : val.isString(); 283 if (str != null) { 284 try { 285 return new Date(Long.parseLong(str.stringValue())); 286 } catch (@SuppressWarnings("unused") NumberFormatException e) { 287 // do nothing - return the default value 288 } 289 } 290 return null; 291 } 292 293 /** 294 * Read an optional int value form a JSON value. 295 * @param val the JSON value that should represent the int. 296 * @return the int from the JSON or 0 reading the int fails. 297 */ 298 private int readOptionalInt(JSONValue val) { 299 300 JSONString str = null == val ? null : val.isString(); 301 if (str != null) { 302 try { 303 return Integer.valueOf(str.stringValue()).intValue(); 304 } catch (@SuppressWarnings("unused") NumberFormatException e) { 305 // Do nothing, return default value 306 } 307 } 308 return 0; 309 } 310 311 /** 312 * Read an optional month value form a JSON value. 313 * @param val the JSON value that should represent the month. 314 * @return the month from the JSON or null if reading the month fails. 315 */ 316 private Month readOptionalMonth(JSONValue val) { 317 318 String str = readOptionalString(val); 319 if (null != str) { 320 try { 321 return Month.valueOf(str); 322 } catch (@SuppressWarnings("unused") IllegalArgumentException e) { 323 // Do nothing -return the default value 324 } 325 } 326 return null; 327 } 328 329 /** 330 * Read an optional string value form a JSON value. 331 * @param val the JSON value that should represent the string. 332 * @return the string from the JSON or null if reading the string fails. 333 */ 334 private String readOptionalString(JSONValue val) { 335 336 JSONString str = null == val ? null : val.isString(); 337 if (str != null) { 338 return str.stringValue(); 339 } 340 return null; 341 } 342 343 /** 344 * Read an optional uuid stored as JSON string. 345 * @param val the JSON value to read the uuid from. 346 * @return the uuid, or <code>null</code> if the uuid can not be read. 347 */ 348 private CmsUUID readOptionalUUID(JSONValue val) { 349 350 String id = readOptionalString(val); 351 if (null != id) { 352 try { 353 CmsUUID uuid = CmsUUID.valueOf(id); 354 return uuid; 355 } catch (@SuppressWarnings("unused") NumberFormatException e) { 356 // Do nothing, just return null 357 } 358 } 359 return null; 360 } 361 362 /** 363 * Read pattern information from the provided JSON object. 364 * @param patternJson the JSON object containing the pattern information. 365 */ 366 private void readPattern(JSONObject patternJson) { 367 368 setPatternType(readPatternType(patternJson.get(JsonKey.PATTERN_TYPE))); 369 setInterval(readOptionalInt(patternJson.get(JsonKey.PATTERN_INTERVAL))); 370 setWeekDays(readWeekDays(patternJson.get(JsonKey.PATTERN_WEEKDAYS))); 371 setDayOfMonth(readOptionalInt(patternJson.get(JsonKey.PATTERN_DAY_OF_MONTH))); 372 setEveryWorkingDay(readOptionalBoolean(patternJson.get(JsonKey.PATTERN_EVERYWORKINGDAY))); 373 setWeeksOfMonth(readWeeksOfMonth(patternJson.get(JsonKey.PATTERN_WEEKS_OF_MONTH))); 374 setIndividualDates(readDates(patternJson.get(JsonKey.PATTERN_DATES))); 375 setMonth(readOptionalMonth(patternJson.get(JsonKey.PATTERN_MONTH))); 376 377 } 378 379 /** 380 * Reads the pattern type from the provided JSON. Defaults to type NONE. 381 * @param val the JSON object containing the pattern type information. 382 * @return the pattern type. 383 */ 384 private PatternType readPatternType(JSONValue val) { 385 386 PatternType patterntype; 387 try { 388 String str = readOptionalString(val); 389 patterntype = PatternType.valueOf(str); 390 } catch (@SuppressWarnings("unused") IllegalArgumentException e) { 391 patterntype = PatternType.NONE; 392 } 393 return patterntype; 394 } 395 396 /** 397 * Read a single weekday from the provided JSON value. 398 * @param val the value to read the week day from. 399 * @return the week day read 400 * @throws IllegalArgumentException thrown if the provided JSON value is not the representation of a week day. 401 */ 402 private WeekDay readWeekDay(JSONValue val) throws IllegalArgumentException { 403 404 String str = readOptionalString(val); 405 if (null != str) { 406 return WeekDay.valueOf(str); 407 } 408 throw new IllegalArgumentException(); 409 } 410 411 /** 412 * Reads the weekday information. 413 * @param val the JSON object containing the weekday information. 414 * @return the weekdays extracted, defaults to the empty list, if no information is found. 415 */ 416 private SortedSet<WeekDay> readWeekDays(JSONValue val) { 417 418 JSONArray array = null == val ? null : val.isArray(); 419 if (null != array) { 420 SortedSet<WeekDay> result = new TreeSet<>(); 421 for (int i = 0; i < array.size(); i++) { 422 try { 423 result.add(readWeekDay(array.get(i))); 424 } catch (@SuppressWarnings("unused") IllegalArgumentException e) { 425 // Just skip 426 } 427 } 428 return result; 429 } 430 return new TreeSet<>(); 431 } 432 433 /** 434 * Read "weeks of month" information from JSON. 435 * @param json the JSON where information is read from. 436 * @return the list of weeks read, defaults to the empty list. 437 */ 438 private SortedSet<WeekOfMonth> readWeeksOfMonth(JSONValue json) { 439 440 JSONArray array = null == json ? null : json.isArray(); 441 if (null != array) { 442 SortedSet<WeekOfMonth> result = new TreeSet<>(); 443 for (int i = 0; i < array.size(); i++) { 444 String weekStr = readOptionalString(array.get(i)); 445 try { 446 WeekOfMonth week = WeekOfMonth.valueOf(weekStr); 447 result.add(week); 448 } catch (@SuppressWarnings("unused") IllegalArgumentException | NullPointerException e) { 449 // Just skip 450 } 451 } 452 return result; 453 } 454 return new TreeSet<>(); 455 } 456 457 /** 458 * Convert a list of objects to a JSON array with the string representations of that objects. 459 * @param list the list of objects. 460 * @return the JSON array with the string representations. 461 */ 462 private JSONValue toJsonStringList(Collection<? extends Object> list) { 463 464 if (null != list) { 465 JSONArray array = new JSONArray(); 466 for (Object o : list) { 467 array.set(array.size(), new JSONString(o.toString())); 468 } 469 return array; 470 } else { 471 return null; 472 } 473 } 474 475 /** 476 * Try to set the value from the provided Json string. 477 * @param value the value to set. 478 * @throws Exception thrown if parsing fails. 479 */ 480 private void tryToSetParsedValue(String value) throws Exception { 481 482 JSONObject json = JSONParser.parseStrict(value).isObject(); 483 JSONValue val = json.get(JsonKey.START); 484 setStart(readOptionalDate(val)); 485 val = json.get(JsonKey.END); 486 setEnd(readOptionalDate(val)); 487 setWholeDay(readOptionalBoolean(json.get(JsonKey.WHOLE_DAY))); 488 JSONObject patternJson = json.get(JsonKey.PATTERN).isObject(); 489 readPattern(patternJson); 490 setExceptions(readDates(json.get(JsonKey.EXCEPTIONS))); 491 setSeriesEndDate(readOptionalDate(json.get(JsonKey.SERIES_ENDDATE))); 492 setOccurrences(readOptionalInt(json.get(JsonKey.SERIES_OCCURRENCES))); 493 setDerivedEndType(); 494 setCurrentTillEnd(readOptionalBoolean(json.get(JsonKey.CURRENT_TILL_END))); 495 setParentSeriesId(readOptionalUUID(json.get(JsonKey.PARENT_SERIES))); 496 497 } 498}