resolving conflicts after merge of ticket_4716 into develop
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / common / TimePeriod.java
1 /**
2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9
10 package eu.etaxonomy.cdm.model.common;
11
12 import java.io.Serializable;
13 import java.util.Calendar;
14 import java.util.Date;
15
16 import javax.persistence.Embeddable;
17 import javax.persistence.Transient;
18 import javax.xml.bind.annotation.XmlAccessType;
19 import javax.xml.bind.annotation.XmlAccessorType;
20 import javax.xml.bind.annotation.XmlElement;
21 import javax.xml.bind.annotation.XmlRootElement;
22 import javax.xml.bind.annotation.XmlType;
23 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
24
25 import org.apache.commons.lang.StringUtils;
26 import org.apache.log4j.Logger;
27 import org.hibernate.annotations.Type;
28 import org.hibernate.search.annotations.Analyze;
29 import org.hibernate.search.annotations.Field;
30 import org.hibernate.search.annotations.FieldBridge;
31 import org.joda.time.DateTime;
32 import org.joda.time.DateTimeFieldType;
33 import org.joda.time.LocalDate;
34 import org.joda.time.Partial;
35 import org.joda.time.ReadableInstant;
36 import org.joda.time.format.DateTimeFormatter;
37
38 import com.fasterxml.jackson.annotation.JsonIgnore;
39
40 import eu.etaxonomy.cdm.common.CdmUtils;
41 import eu.etaxonomy.cdm.hibernate.search.PartialBridge;
42 import eu.etaxonomy.cdm.jaxb.PartialAdapter;
43 import eu.etaxonomy.cdm.strategy.cache.common.TimePeriodPartialFormatter;
44
45 /**
46 * @author m.doering
47 * @created 08-Nov-2007 13:07:00
48 * @updated 05-Dec-2008 23:00:05
49 * @updated 14-Jul-2013 move parser methods to TimePeriodParser
50 */
51 @XmlAccessorType(XmlAccessType.FIELD)
52 @XmlType(name = "TimePeriod", propOrder = {
53 "start",
54 "end",
55 "freeText"
56 })
57 @XmlRootElement(name = "TimePeriod")
58 @Embeddable
59 public class TimePeriod implements Cloneable, Serializable {
60 private static final long serialVersionUID = 3405969418194981401L;
61 private static final Logger logger = Logger.getLogger(TimePeriod.class);
62 public static final DateTimeFieldType MONTH_TYPE = DateTimeFieldType.monthOfYear();
63 public static final DateTimeFieldType YEAR_TYPE = DateTimeFieldType.year();
64 public static final DateTimeFieldType DAY_TYPE = DateTimeFieldType.dayOfMonth();
65
66 @XmlElement(name = "Start")
67 @XmlJavaTypeAdapter(value = PartialAdapter.class)
68 @Type(type="partialUserType")
69 @Field(analyze = Analyze.NO)
70 @FieldBridge(impl = PartialBridge.class)
71 @JsonIgnore // currently used for swagger model scanner
72 private Partial start;
73
74 @XmlElement(name = "End")
75 @XmlJavaTypeAdapter(value = PartialAdapter.class)
76 @Type(type="partialUserType")
77 @Field(analyze = Analyze.NO)
78 @FieldBridge(impl = PartialBridge.class)
79 @JsonIgnore // currently used for swagger model scanner
80 private Partial end;
81
82
83 @XmlElement(name = "FreeText")
84 private String freeText;
85
86 // ********************** FACTORY METHODS **************************/
87
88 /**
89 * Factory method
90 * @return
91 */
92 public static TimePeriod NewInstance(){
93 return new TimePeriod();
94 }
95
96
97 /**
98 * Factory method
99 * @return
100 */
101 public static TimePeriod NewInstance(Partial startDate){
102 return new TimePeriod(startDate);
103 }
104
105
106 /**
107 * Factory method
108 * @return
109 */
110 public static TimePeriod NewInstance(Partial startDate, Partial endDate){
111 return new TimePeriod(startDate, endDate);
112 }
113
114
115 /**
116 * Factory method
117 * @return
118 */
119 public static TimePeriod NewInstance(Integer year){
120 Integer endYear = null;
121 return NewInstance(year, endYear);
122 }
123
124 /**
125 * Factory method
126 * @return
127 */
128 public static TimePeriod NewInstance(Integer startYear, Integer endYear){
129 Partial startDate = null;
130 Partial endDate = null;
131 if (startYear != null){
132 startDate = new Partial().with(YEAR_TYPE, startYear);
133 }
134 if (endYear != null){
135 endDate = new Partial().with(YEAR_TYPE, endYear);
136 }
137 return new TimePeriod(startDate, endDate);
138 }
139
140
141
142 /**
143 * Factory method to create a TimePeriod from a <code>Calendar</code>. The Calendar is stored as the starting instant.
144 * @return
145 */
146 public static TimePeriod NewInstance(Calendar startCalendar){
147 return NewInstance(startCalendar, null);
148 }
149
150 /**
151 * Factory method to create a TimePeriod from a <code>ReadableInstant</code>(e.g. <code>DateTime</code>).
152 * The <code>ReadableInstant</code> is stored as the starting instant.
153 * @return
154 */
155 public static TimePeriod NewInstance(ReadableInstant readableInstant){
156 return NewInstance(readableInstant, null);
157 }
158
159 /**
160 * Factory method to create a TimePeriod from a starting and an ending <code>Calendar</code>
161 * @return
162 */
163 public static TimePeriod NewInstance(Calendar startCalendar, Calendar endCalendar){
164 Partial startDate = null;
165 Partial endDate = null;
166 if (startCalendar != null){
167 startDate = calendarToPartial(startCalendar);
168 }
169 if (endCalendar != null){
170 endDate = calendarToPartial(endCalendar);
171 }
172 return new TimePeriod(startDate, endDate);
173 }
174
175 /**
176 * Factory method to create a TimePeriod from a starting and an ending <code>Date</code>
177 * @return TimePeriod
178 */
179 public static TimePeriod NewInstance(Date startDate, Date endDate){
180 //TODO conversion untested, implemented according to http://www.roseindia.net/java/java-conversion/datetocalender.shtml
181 Calendar calStart = null;
182 Calendar calEnd = null;
183 if (startDate != null){
184 calStart = Calendar.getInstance();
185 calStart.setTime(startDate);
186 }
187 if (endDate != null){
188 calEnd = Calendar.getInstance();
189 calEnd.setTime(endDate);
190 }
191 return NewInstance(calStart, calEnd);
192 }
193
194
195 /**
196 * Factory method to create a TimePeriod from a starting and an ending <code>ReadableInstant</code>(e.g. <code>DateTime</code>)
197 * @return
198 */
199 public static TimePeriod NewInstance(ReadableInstant startInstant, ReadableInstant endInstant){
200 Partial startDate = null;
201 Partial endDate = null;
202 if (startInstant != null){
203 startDate = readableInstantToPartial(startInstant);
204 }
205 if (endInstant != null){
206 endDate = readableInstantToPartial(endInstant);
207 }
208 return new TimePeriod(startDate, endDate);
209 }
210
211 //****************** CONVERTERS ******************/
212
213 /**
214 * Transforms a {@link Calendar} into a <code>Partial</code>
215 * @param calendar
216 * @return
217 */
218 public static Partial calendarToPartial(Calendar calendar){
219 LocalDate ld = new LocalDate(calendar);
220 Partial partial = new Partial(ld);
221 return partial;
222 }
223
224 /**
225 * Transforms a {@link ReadableInstant} into a <code>Partial</code>
226 * @param calendar
227 * @return
228 */
229 public static Partial readableInstantToPartial(ReadableInstant readableInstant){
230 DateTime dt = readableInstant.toInstant().toDateTime();
231 LocalDate ld = dt.toLocalDate();
232 Partial partial = new Partial(ld);
233 return partial;
234 }
235
236
237 public static Integer getPartialValue(Partial partial, DateTimeFieldType type){
238 if (partial == null || ! partial.isSupported(type)){
239 return null;
240 }else{
241 return partial.get(type);
242 }
243 }
244
245
246
247 //*********************** CONSTRUCTOR *********************************/
248
249 /**
250 * Constructor
251 */
252 protected TimePeriod() {
253 super();
254 }
255 public TimePeriod(Partial startDate) {
256 start=startDate;
257 }
258 public TimePeriod(Partial startDate, Partial endDate) {
259 start=startDate;
260 end=endDate;
261 }
262
263 //******************* GETTER / SETTER ************************************/
264
265
266 @JsonIgnore // currently used for swagger model scanner
267 public Partial getStart() {
268 return start;
269 }
270
271 public void setStart(Partial start) {
272 this.start = start;
273 }
274
275
276 @JsonIgnore // currently used for swagger model scanner
277 public Partial getEnd() {
278 return end;
279 }
280
281 public void setEnd(Partial end) {
282 this.end = end;
283 }
284
285 /**
286 * For time periods that need to store more information than the one
287 * that can be stored in <code>start</code> and <code>end</code>.
288 * If free text is not <code>null</null> {@link #toString()} will always
289 * return the free text value.
290 * <BR>Use {@link #toString()} for public use.
291 * @return the freeText
292 */
293 public String getFreeText() {
294 return freeText;
295 }
296
297
298 /**
299 * Use {@link #parseSingleDate(String)} for public use.
300 * @param freeText the freeText to set
301 */
302 public void setFreeText(String freeText) {
303 this.freeText = freeText;
304 }
305
306
307 //******************* Transient METHODS ************************************/
308
309 /**
310 * True, if this time period represents a period not a single point in time.
311 * This is by definition, that the time period has a start and an end value,
312 * and both have a year value that is not null
313 * @return
314 */
315 @Transient
316 public boolean isPeriod(){
317 if (getStartYear() != null && getEndYear() != null ){
318 return true;
319 }else{
320 return false;
321 }
322 }
323
324 /**
325 * True, if there is no start date and no end date and no freetext representation exists.
326 * @return
327 */
328 @Transient
329 public boolean isEmpty(){
330 if (StringUtils.isBlank(this.getFreeText()) && start == null && end == null ){
331 return true;
332 }else{
333 return false;
334 }
335 }
336
337
338
339 @Transient
340 public String getYear(){
341 String result = "";
342 if (getStartYear() != null){
343 result += String.valueOf(getStartYear());
344 if (getEndYear() != null){
345 result += "-" + String.valueOf(getEndYear());
346 }
347 }else{
348 if (getEndYear() != null){
349 result += String.valueOf(getEndYear());
350 }
351 }
352 return result;
353 }
354
355 @Transient
356 public Integer getStartYear(){
357 return getPartialValue(start, YEAR_TYPE);
358 }
359
360 @Transient
361 public Integer getStartMonth(){
362 return getPartialValue(start, MONTH_TYPE);
363 }
364
365 @Transient
366 public Integer getStartDay(){
367 return getPartialValue(start, DAY_TYPE);
368 }
369
370 @Transient
371 public Integer getEndYear(){
372 return getPartialValue(end, YEAR_TYPE);
373 }
374
375 @Transient
376 public Integer getEndMonth(){
377 return getPartialValue(end, MONTH_TYPE);
378 }
379
380 @Transient
381 public Integer getEndDay(){
382 return getPartialValue(end, DAY_TYPE);
383 }
384
385 public TimePeriod setStartYear(Integer year){
386 return setStartField(year, YEAR_TYPE);
387 }
388
389 public TimePeriod setStartMonth(Integer month) throws IndexOutOfBoundsException{
390 return setStartField(month, MONTH_TYPE);
391 }
392
393 public TimePeriod setStartDay(Integer day) throws IndexOutOfBoundsException{
394 return setStartField(day, DAY_TYPE);
395 }
396
397 public TimePeriod setEndYear(Integer year){
398 return setEndField(year, YEAR_TYPE);
399 }
400
401 public TimePeriod setEndMonth(Integer month) throws IndexOutOfBoundsException{
402 return setEndField(month, MONTH_TYPE);
403 }
404
405 public TimePeriod setEndDay(Integer day) throws IndexOutOfBoundsException{
406 return setEndField(day, DAY_TYPE);
407 }
408
409 public static Partial setPartialField(Partial partial, Integer value, DateTimeFieldType type)
410 throws IndexOutOfBoundsException{
411 if (partial == null){
412 partial = new Partial();
413 }
414 if (value == null){
415 return partial.without(type);
416 }else{
417 checkFieldValues(value, type, partial);
418 return partial.with(type, value);
419 }
420 }
421
422 @Transient
423 private TimePeriod setStartField(Integer value, DateTimeFieldType type)
424 throws IndexOutOfBoundsException{
425 start = setPartialField(start, value, type);
426 return this;
427 }
428
429 @Transient
430 private TimePeriod setEndField(Integer value, DateTimeFieldType type)
431 throws IndexOutOfBoundsException{
432 end = setPartialField(end, value, type);
433 return this;
434 }
435
436 // ******************************** internal methods *******************************/
437
438 /**
439 * Throws an IndexOutOfBoundsException if the value does not have a valid value
440 * (e.g. month > 12, month < 1, day > 31, etc.)
441 * @param value
442 * @param type
443 * @throws IndexOutOfBoundsException
444 */
445 private static void checkFieldValues(Integer value, DateTimeFieldType type, Partial partial)
446 throws IndexOutOfBoundsException{
447 int max = 9999999;
448 if (type.equals(MONTH_TYPE)){
449 max = 12;
450 }
451 if (type.equals(DAY_TYPE)){
452 max = 31;
453 Integer month = null;
454 if (partial.isSupported(MONTH_TYPE)){
455 month = partial.get(MONTH_TYPE);
456 }
457 if (month != null){
458 if (month == 2){
459 max = 29;
460 }else if (month == 4 ||month == 6 ||month == 9 ||month == 11){
461 max = 30;
462 }
463 }
464 }
465 if ( (value < 1 || value > max) ){
466 throw new IndexOutOfBoundsException("Value must be between 1 and " + max);
467 }
468 }
469
470 private void initStart(){
471 if (start == null){
472 start = new Partial();
473 }
474 }
475
476 private void initEnd(){
477 if (end == null){
478 end = new Partial();
479 }
480 }
481
482
483 //**************************** to String ****************************************
484
485 /**
486 * Returns the {@link #getFreeText()} value if free text is not <code>null</code>.
487 * Otherwise the concatenation of <code>start</code> and <code>end</code> is returned.
488 *
489 * @see java.lang.Object#toString()
490 */
491 @Override
492 public String toString(){
493 String result = null;
494 DateTimeFormatter formatter = TimePeriodPartialFormatter.NewInstance();
495 if ( StringUtils.isNotBlank(this.getFreeText())){
496 result = this.getFreeText();
497 }else{
498 String strStart = start != null ? start.toString(formatter): null;
499 String strEnd = end != null ? end.toString(formatter): null;
500 result = CdmUtils.concat("-", strStart, strEnd);
501 }
502 return result;
503 }
504
505 //*********** EQUALS **********************************/
506
507
508 /* (non-Javadoc)
509 * @see java.lang.Object#equals(java.lang.Object)
510 */
511 @Override
512 public boolean equals(Object obj) {
513 if (obj == null){
514 return false;
515 }
516 if (! (obj instanceof TimePeriod)){
517 return false;
518 }
519 TimePeriod that = (TimePeriod)obj;
520
521 if (! CdmUtils.nullSafeEqual(this.start, that.start)){
522 return false;
523 }
524 if (! CdmUtils.nullSafeEqual(this.end, that.end)){
525 return false;
526 }
527 if (! CdmUtils.nullSafeEqual(this.freeText, that.freeText)){
528 return false;
529 }
530 return true;
531 }
532
533 /* (non-Javadoc)
534 * @see java.lang.Object#hashCode()
535 */
536 @Override
537 public int hashCode() {
538 int hashCode = 7;
539 hashCode = 29*hashCode +
540 (start== null? 33: start.hashCode()) +
541 (end== null? 39: end.hashCode()) +
542 (freeText== null? 41: freeText.hashCode());
543 return super.hashCode();
544 }
545
546
547 //*********** CLONE **********************************/
548
549
550 /* (non-Javadoc)
551 * @see java.lang.Object#clone()
552 */
553 @Override
554 public Object clone() {
555 try {
556 TimePeriod result = (TimePeriod)super.clone();
557 result.setStart(this.start); //DateTime is immutable
558 result.setEnd(this.end);
559 result.setFreeText(this.freeText);
560 return result;
561 } catch (CloneNotSupportedException e) {
562 logger.warn("Clone not supported exception. Should never occurr !!");
563 return null;
564 }
565 }
566
567
568 }