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