merge hibernate4 migration branch into trunk
[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.text.DateFormat;
14 import java.text.ParsePosition;
15 import java.util.Calendar;
16 import java.util.Date;
17 import java.util.regex.Matcher;
18 import java.util.regex.Pattern;
19
20 import javax.persistence.Embeddable;
21 import javax.persistence.Transient;
22 import javax.xml.bind.annotation.XmlAccessType;
23 import javax.xml.bind.annotation.XmlAccessorType;
24 import javax.xml.bind.annotation.XmlElement;
25 import javax.xml.bind.annotation.XmlRootElement;
26 import javax.xml.bind.annotation.XmlType;
27 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
28
29 import org.apache.log4j.Logger;
30 import org.hibernate.annotations.Type;
31 import org.hibernate.search.annotations.Analyze;
32 import org.hibernate.search.annotations.Field;
33 import org.hibernate.search.annotations.FieldBridge;
34 import org.joda.time.DateTime;
35 import org.joda.time.DateTimeFieldType;
36 import org.joda.time.LocalDate;
37 import org.joda.time.Partial;
38 import org.joda.time.ReadableInstant;
39 import org.joda.time.ReadablePartial;
40 import org.joda.time.format.DateTimeFormatter;
41
42 import eu.etaxonomy.cdm.common.CdmUtils;
43 import eu.etaxonomy.cdm.hibernate.search.PartialBridge;
44 import eu.etaxonomy.cdm.jaxb.PartialAdapter;
45
46 /**
47 * @author m.doering
48 * @version 1.0
49 * @created 08-Nov-2007 13:07:00
50 * @updated 05-Dec-2008 23:00:05
51 */
52 @XmlAccessorType(XmlAccessType.FIELD)
53 @XmlType(name = "TimePeriod", propOrder = {
54 "start",
55 "end",
56 "freeText"
57 })
58 @XmlRootElement(name = "TimePeriod")
59 @Embeddable
60 public class TimePeriod implements Cloneable, Serializable {
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 private Partial start;
72
73 @XmlElement(name = "End")
74 @XmlJavaTypeAdapter(value = PartialAdapter.class)
75 @Type(type="partialUserType")
76 @Field(analyze = Analyze.NO)
77 @FieldBridge(impl = PartialBridge.class)
78 private Partial end;
79
80
81 @XmlElement(name = "FreeText")
82 private String freeText;
83
84
85 /**
86 * Factory method
87 * @return
88 */
89 public static TimePeriod NewInstance(){
90 return new TimePeriod();
91 }
92
93
94 /**
95 * Factory method
96 * @return
97 */
98 public static TimePeriod NewInstance(Partial startDate){
99 return new TimePeriod(startDate);
100 }
101
102
103 /**
104 * Factory method
105 * @return
106 */
107 public static TimePeriod NewInstance(Partial startDate, Partial endDate){
108 return new TimePeriod(startDate, endDate);
109 }
110
111
112 /**
113 * Factory method
114 * @return
115 */
116 public static TimePeriod NewInstance(Integer year){
117 Integer endYear = null;
118 return NewInstance(year, endYear);
119 }
120
121 /**
122 * Factory method
123 * @return
124 */
125 public static TimePeriod NewInstance(Integer startYear, Integer endYear){
126 Partial startDate = null;
127 Partial endDate = null;
128 if (startYear != null){
129 startDate = new Partial().with(YEAR_TYPE, startYear);
130 }
131 if (endYear != null){
132 endDate = new Partial().with(YEAR_TYPE, endYear);
133 }
134 return new TimePeriod(startDate, endDate);
135 }
136
137
138
139 /**
140 * Factory method to create a TimePeriod from a <code>Calendar</code>. The Calendar is stored as the starting instant.
141 * @return
142 */
143 public static TimePeriod NewInstance(Calendar startCalendar){
144 return NewInstance(startCalendar, null);
145 }
146
147 /**
148 * Factory method to create a TimePeriod from a <code>ReadableInstant</code>(e.g. <code>DateTime</code>).
149 * The <code>ReadableInstant</code> is stored as the starting instant.
150 * @return
151 */
152 public static TimePeriod NewInstance(ReadableInstant readableInstant){
153 return NewInstance(readableInstant, null);
154 }
155
156 /**
157 * Factory method to create a TimePeriod from a starting and an ending <code>Calendar</code>
158 * @return
159 */
160 public static TimePeriod NewInstance(Calendar startCalendar, Calendar endCalendar){
161 Partial startDate = null;
162 Partial endDate = null;
163 if (startCalendar != null){
164 startDate = calendarToPartial(startCalendar);
165 }
166 if (endCalendar != null){
167 endDate = calendarToPartial(endCalendar);
168 }
169 return new TimePeriod(startDate, endDate);
170 }
171
172 /**
173 * Factory method to create a TimePeriod from a starting and an ending <code>Date</code>
174 * @return TimePeriod
175 */
176 public static TimePeriod NewInstance(Date startDate, Date endDate){
177 //TODO conversion untested, implemented according to http://www.roseindia.net/java/java-conversion/datetocalender.shtml
178 Calendar calStart = null;
179 Calendar calEnd = null;
180 if (startDate != null){
181 calStart = Calendar.getInstance();
182 calStart.setTime(startDate);
183 }
184 if (endDate != null){
185 calEnd = Calendar.getInstance();
186 calEnd.setTime(endDate);
187 }
188 return NewInstance(calStart, calEnd);
189 }
190
191
192 /**
193 * Factory method to create a TimePeriod from a starting and an ending <code>ReadableInstant</code>(e.g. <code>DateTime</code>)
194 * @return
195 */
196 public static TimePeriod NewInstance(ReadableInstant startInstant, ReadableInstant endInstant){
197 Partial startDate = null;
198 Partial endDate = null;
199 if (startInstant != null){
200 startDate = readableInstantToPartial(startInstant);
201 }
202 if (endInstant != null){
203 endDate = readableInstantToPartial(endInstant);
204 }
205 return new TimePeriod(startDate, endDate);
206 }
207
208
209 /**
210 * Transforms a <code>Calendar</code> 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 <code>Calendar</code> 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 * Constructor
234 */
235 protected TimePeriod() {
236 super();
237 }
238 public TimePeriod(Partial startDate) {
239 start=startDate;
240 }
241 public TimePeriod(Partial startDate, Partial endDate) {
242 start=startDate;
243 end=endDate;
244 }
245
246 /**
247 * True, if this time period represents a period not a single point in time.
248 * This is by definition, that the time period has a start and an end value,
249 * and both have a year value that is not null
250 * @return
251 */
252 @Transient
253 public boolean isPeriod(){
254 if (getStartYear() != null && getEndYear() != null ){
255 return true;
256 }else{
257 return false;
258 }
259 }
260
261 /**
262 * True, if there is no start date and no end date and no freetext representation exists.
263 * @return
264 */
265 @Transient
266 public boolean isEmpty(){
267 if (CdmUtils.isEmpty(this.getFreeText()) && start == null && end == null ){
268 return true;
269 }else{
270 return false;
271 }
272 }
273
274
275 public Partial getStart() {
276 return start;
277 }
278
279 public void setStart(Partial start) {
280 this.start = start;
281 }
282
283 public Partial getEnd() {
284 return end;
285 }
286
287 public void setEnd(Partial end) {
288 this.end = end;
289 }
290
291 /**
292 * For time periods that need to store more information than the one
293 * that can be stored in <code>start</code> and <code>end</code>.
294 * If free text is not <code>null</null> {@link #toString()} will always
295 * return the free text value.
296 * <BR>Use {@link #toString()} for public use.
297 * @return the freeText
298 */
299 public String getFreeText() {
300 return freeText;
301 }
302
303
304 /**
305 * Use {@link #parseSingleDate(String)} for public use.
306 * @param freeText the freeText to set
307 */
308 public void setFreeText(String freeText) {
309 this.freeText = freeText;
310 }
311
312
313 @Transient
314 public String getYear(){
315 String result = "";
316 if (getStartYear() != null){
317 result += String.valueOf(getStartYear());
318 if (getEndYear() != null){
319 result += "-" + String.valueOf(getEndYear());
320 }
321 }else{
322 if (getEndYear() != null){
323 result += String.valueOf(getEndYear());
324 }
325 }
326 return result;
327 }
328
329 @Transient
330 public Integer getStartYear(){
331 return getPartialValue(start, YEAR_TYPE);
332 }
333
334 @Transient
335 public Integer getStartMonth(){
336 return getPartialValue(start, MONTH_TYPE);
337 }
338
339 @Transient
340 public Integer getStartDay(){
341 return getPartialValue(start, DAY_TYPE);
342 }
343
344 @Transient
345 public Integer getEndYear(){
346 return getPartialValue(end, YEAR_TYPE);
347 }
348
349 @Transient
350 public Integer getEndMonth(){
351 return getPartialValue(end, MONTH_TYPE);
352 }
353
354 @Transient
355 public Integer getEndDay(){
356 return getPartialValue(end, DAY_TYPE);
357 }
358
359 public static Integer getPartialValue(Partial partial, DateTimeFieldType type){
360 if (partial == null || ! partial.isSupported(type)){
361 return null;
362 }else{
363 return partial.get(type);
364 }
365
366 }
367
368 public TimePeriod setStartYear(Integer year){
369 return setStartField(year, YEAR_TYPE);
370 }
371
372 public TimePeriod setStartMonth(Integer month) throws IndexOutOfBoundsException{
373 return setStartField(month, MONTH_TYPE);
374 }
375
376 public TimePeriod setStartDay(Integer day) throws IndexOutOfBoundsException{
377 return setStartField(day, DAY_TYPE);
378 }
379
380 public TimePeriod setEndYear(Integer year){
381 return setEndField(year, YEAR_TYPE);
382 }
383
384 public TimePeriod setEndMonth(Integer month) throws IndexOutOfBoundsException{
385 return setEndField(month, MONTH_TYPE);
386 }
387
388 public TimePeriod setEndDay(Integer day) throws IndexOutOfBoundsException{
389 return setEndField(day, DAY_TYPE);
390 }
391
392 public static Partial setPartialField(Partial partial, Integer value, DateTimeFieldType type)
393 throws IndexOutOfBoundsException{
394 if (partial == null){
395 partial = new Partial();
396 }
397 if (value == null){
398 return partial.without(type);
399 }else{
400 checkFieldValues(value, type, partial);
401 return partial.with(type, value);
402 }
403 }
404
405 private TimePeriod setStartField(Integer value, DateTimeFieldType type)
406 throws IndexOutOfBoundsException{
407 start = setPartialField(start, value, type);
408 return this;
409 }
410
411 private TimePeriod setEndField(Integer value, DateTimeFieldType type)
412 throws IndexOutOfBoundsException{
413 end = setPartialField(end, value, type);
414 return this;
415 }
416
417 /**
418 * Throws an IndexOutOfBoundsException if the value does not have a valid value
419 * (e.g. month > 12, month < 1, day > 31, etc.)
420 * @param value
421 * @param type
422 * @throws IndexOutOfBoundsException
423 */
424 private static void checkFieldValues(Integer value, DateTimeFieldType type, Partial partial)
425 throws IndexOutOfBoundsException{
426 int max = 9999999;
427 if (type.equals(MONTH_TYPE)){
428 max = 12;
429 }
430 if (type.equals(DAY_TYPE)){
431 max = 31;
432 Integer month = null;
433 if (partial.isSupported(MONTH_TYPE)){
434 month = partial.get(MONTH_TYPE);
435 }
436 if (month != null){
437 if (month == 2){
438 max = 29;
439 }else if (month == 4 ||month == 6 ||month == 9 ||month == 11){
440 max = 30;
441 }
442 }
443 }
444 if ( (value < 1 || value > max) ){
445 throw new IndexOutOfBoundsException("Value must be between 1 and " + max);
446 }
447 }
448
449 private void initStart(){
450 if (start == null){
451 start = new Partial();
452 }
453 }
454
455 private void initEnd(){
456 if (end == null){
457 end = new Partial();
458 }
459 }
460
461
462 //patter for first year in string;
463 private static final Pattern firstYearPattern = Pattern.compile("\\d{4}");
464 //case "1806"[1807];
465 private static final Pattern uncorrectYearPatter = Pattern.compile("\"\\d{4}\"\\s*\\[\\d{4}\\]");
466 //case fl. 1806 or c. 1806 or fl. 1806?
467 private static final Pattern prefixedYearPattern = Pattern.compile("(fl|c)\\.\\s*\\d{4}(\\s*-\\s*\\d{4})?\\??");
468 //standard
469 private static final Pattern standardPattern = Pattern.compile("\\s*\\d{2,4}(\\s*-(\\s*\\d{2,4})?)?");
470 private static final String strDotDate = "[0-3]?\\d\\.[01]?\\d\\.\\d{4,4}";
471 private static final String strDotDatePeriodPattern = String.format("%s(\\s*-\\s*%s?)?", strDotDate, strDotDate);
472 private static final Pattern dotDatePattern = Pattern.compile(strDotDatePeriodPattern);
473
474
475 public static TimePeriod parseString(TimePeriod timePeriod, String periodString){
476 //TODO move to parser class
477 //TODO until now only quick and dirty (and partly wrong)
478 TimePeriod result = timePeriod;
479
480 if(timePeriod == null){
481 return timePeriod;
482 }
483
484 if (periodString == null){
485 return result;
486 }
487 periodString = periodString.trim();
488
489 result.setFreeText(null);
490 Date date;
491
492 //case "1806"[1807];
493 if (uncorrectYearPatter.matcher(periodString).matches()){
494 result.setFreeText(periodString);
495 String realYear = periodString.split("\\[")[1];
496 realYear = realYear.replace("]", "");
497 result.setStartYear(Integer.valueOf(realYear));
498 result.setFreeText(periodString);
499 //case fl. 1806 or c. 1806 or fl. 1806?
500 }else if(prefixedYearPattern.matcher(periodString).matches()){
501 result.setFreeText(periodString);
502 Matcher yearMatcher = firstYearPattern.matcher(periodString);
503 yearMatcher.find();
504 String startYear = yearMatcher.group();
505 result.setStartYear(Integer.valueOf(startYear));
506 if (yearMatcher.find()){
507 String endYear = yearMatcher.group();
508 result.setEndYear(Integer.valueOf(endYear));
509 }
510 }else if (dotDatePattern.matcher(periodString).matches()){
511 parseDotDatePattern(periodString, result);
512 }else if (standardPattern.matcher(periodString).matches()){
513 parseStandardPattern(periodString, result);
514 //TODO first check ambiguity of parser results e.g. for 7/12/11
515 // }else if (isDateString(periodString)){
516 // String[] startEnd = makeStartEnd(periodString);
517 // String start = startEnd[0];
518 // DateTime startDateTime = dateStringParse(start, true);
519 // result.setStart(startDateTime);
520 // if (startEnd.length > 1){
521 // DateTime endDateTime = dateStringParse(startEnd[1], true);
522 // ;
523 // result.setEnd(endDateTime.toLocalDate());
524 // }
525
526 }else{
527 result.setFreeText(periodString);
528 }
529 return result;
530 }
531
532 private static boolean isDateString(String periodString) {
533 String[] startEnd = makeStartEnd(periodString);
534 String start = startEnd[0];
535 DateTime startDateTime = dateStringParse(start, true);
536 if (startDateTime == null){
537 return false;
538 }
539 if (startEnd.length > 1){
540 DateTime endDateTime = dateStringParse(startEnd[1], true);
541 if (endDateTime != null){
542 return true;
543 }
544 }
545 return false;
546 }
547
548
549 /**
550 * @param periodString
551 * @return
552 */
553 private static String[] makeStartEnd(String periodString) {
554 String[] startEnd = new String[]{periodString};
555 if (periodString.contains("-") && periodString.matches("^-{2,}-^-{2,}")){
556 startEnd = periodString.split("-");
557 }
558 return startEnd;
559 }
560
561
562 private static DateTime dateStringParse(String string, boolean strict) {
563 DateFormat dateFormat = DateFormat.getDateInstance();
564 ParsePosition pos = new ParsePosition(0);
565 Date a = dateFormat.parse(string, pos);
566 if (a == null || pos.getIndex() != string.length()){
567 return null;
568 }
569 Calendar cal = Calendar.getInstance();
570 cal.setTime(a);
571 DateTime result = new DateTime(cal);
572 return result;
573 }
574
575
576 /**
577 * @param periodString
578 * @param result
579 */
580 private static void parseDotDatePattern(String periodString,TimePeriod result) {
581 String[] dates = periodString.split("-");
582 Partial dtStart = null;
583 Partial dtEnd = null;
584
585 if (dates.length > 2 || dates.length <= 0){
586 logger.warn("More than 1 '-' in period String: " + periodString);
587 result.setFreeText(periodString);
588 }else {
589 try {
590 //start
591 if (! CdmUtils.isEmpty(dates[0])){
592 dtStart = parseSingleDotDate(dates[0].trim());
593 }
594
595 //end
596 if (dates.length >= 2 && ! CdmUtils.isEmpty(dates[1])){
597 dtEnd = parseSingleDotDate(dates[1].trim());
598 }
599
600 result.setStart(dtStart);
601 result.setEnd(dtEnd);
602 } catch (IllegalArgumentException e) {
603 //logger.warn(e.getMessage());
604 result.setFreeText(periodString);
605 }
606 }
607 }
608
609
610 /**
611 * @param periodString
612 * @param result
613 */
614 private static void parseStandardPattern(String periodString,
615 TimePeriod result) {
616 String[] years = periodString.split("-");
617 Partial dtStart = null;
618 Partial dtEnd = null;
619
620 if (years.length > 2 || years.length <= 0){
621 logger.warn("More than 1 '-' in period String: " + periodString);
622 }else {
623 try {
624 //start
625 if (! CdmUtils.isEmpty(years[0])){
626 dtStart = parseSingleDate(years[0].trim());
627 }
628
629 //end
630 if (years.length >= 2 && ! CdmUtils.isEmpty(years[1])){
631 years[1] = years[1].trim();
632 if (years[1].length()==2 && dtStart != null && dtStart.isSupported(DateTimeFieldType.year())){
633 years[1] = String.valueOf(dtStart.get(DateTimeFieldType.year())/100) + years[1];
634 }
635 dtEnd = parseSingleDate(years[1]);
636 }
637
638 result.setStart(dtStart);
639 result.setEnd(dtEnd);
640 } catch (IllegalArgumentException e) {
641 //logger.warn(e.getMessage());
642 result.setFreeText(periodString);
643 }
644 }
645 }
646
647 public static TimePeriod parseString(String strPeriod) {
648 TimePeriod timePeriod = TimePeriod.NewInstance();
649 return parseString(timePeriod, strPeriod);
650 }
651
652
653 protected static Partial parseSingleDate(String singleDateString) throws IllegalArgumentException{
654 //FIXME until now only quick and dirty and incomplete
655 Partial partial = new Partial();
656 singleDateString = singleDateString.trim();
657 if (CdmUtils.isNumeric(singleDateString)){
658 try {
659 Integer year = Integer.valueOf(singleDateString.trim());
660 if (year < 1000 && year > 2100){
661 logger.warn("Not a valid year: " + year + ". Year must be between 1000 and 2100");
662 }else if (year < 1700 && year > 2100){
663 logger.warn("Not a valid taxonomic year: " + year + ". Year must be between 1750 and 2100");
664 partial = partial.with(YEAR_TYPE, year);
665 }else{
666 partial = partial.with(YEAR_TYPE, year);
667 }
668 } catch (NumberFormatException e) {
669 logger.debug("Not a Integer format in getCalendar()");
670 throw new IllegalArgumentException(e);
671 }
672 }else{
673 throw new IllegalArgumentException("Until now only years can be parsed as single dates. But date is: " + singleDateString);
674 }
675 return partial;
676
677 }
678
679 protected static Partial parseSingleDotDate(String singleDateString) throws IllegalArgumentException{
680 Partial partial = new Partial();
681 singleDateString = singleDateString.trim();
682 String[] split = singleDateString.split("\\.");
683 int length = split.length;
684 if (length > 3){
685 throw new IllegalArgumentException(String.format("More than 2 dots in date '%s'", singleDateString));
686 }
687 String strYear = split[split.length-1];
688 String strMonth = length >= 2? split[split.length-2]: null;
689 String strDay = length >= 3? split[split.length-3]: null;
690
691
692 try {
693 Integer year = Integer.valueOf(strYear.trim());
694 Integer month = Integer.valueOf(strMonth.trim());
695 Integer day = Integer.valueOf(strDay.trim());
696 if (year < 1000 && year > 2100){
697 logger.warn("Not a valid year: " + year + ". Year must be between 1000 and 2100");
698 }else if (year < 1700 && year > 2100){
699 logger.warn("Not a valid taxonomic year: " + year + ". Year must be between 1750 and 2100");
700 partial = partial.with(YEAR_TYPE, year);
701 }else{
702 partial = partial.with(YEAR_TYPE, year);
703 }
704 if (month != null && month != 0){
705 partial = partial.with(MONTH_TYPE, month);
706 }
707 if (day != null && day != 0){
708 partial = partial.with(DAY_TYPE, day);
709 }
710 } catch (NumberFormatException e) {
711 logger.debug("Not a Integer format somewhere in " + singleDateString);
712 throw new IllegalArgumentException(e);
713 }
714 return partial;
715
716 }
717
718
719
720 private class TimePeriodPartialFormatter extends DateTimeFormatter{
721 private TimePeriodPartialFormatter(){
722 super(null, null);
723 }
724 @Override
725 public String print(ReadablePartial partial){
726 //TODO
727 String result = "";
728 String year = (partial.isSupported(YEAR_TYPE))? String.valueOf(partial.get(YEAR_TYPE)):null;
729 String month = (partial.isSupported(MONTH_TYPE))? String.valueOf(partial.get(MONTH_TYPE)):null;;
730 String day = (partial.isSupported(DAY_TYPE))? String.valueOf(partial.get(DAY_TYPE)):null;;
731
732 if (month !=null){
733 if (year == null){
734 year = "xxxx";
735 }
736 }
737 if (day != null){
738 if (month == null){
739 month = "xx";
740 }
741 if (year == null){
742 year = "xxxx";
743 }
744 }
745 result = (day != null)? day + "." : "";
746 result += (month != null)? month + "." : "";
747 result += (year != null)? year : "";
748
749 return result;
750 }
751
752 }
753
754 //**************************** to String ****************************************
755
756 /**
757 * Returns the {@link #getFreeText()} value if free text is not <code>null</code>.
758 * Otherwise the concatenation of <code>start</code> and <code>end</code> is returned.
759 *
760 * @see java.lang.Object#toString()
761 */
762 @Override
763 public String toString(){
764 String result = null;
765 DateTimeFormatter formatter = new TimePeriodPartialFormatter();
766 if ( CdmUtils.isNotEmpty(this.getFreeText())){
767 result = this.getFreeText();
768 }else{
769 String strStart = start != null ? start.toString(formatter): null;
770 String strEnd = end != null ? end.toString(formatter): null;
771 result = CdmUtils.concat("-", strStart, strEnd);
772 }
773 return result;
774 }
775
776 //*********** EQUALS **********************************/
777
778
779 /* (non-Javadoc)
780 * @see java.lang.Object#equals(java.lang.Object)
781 */
782 @Override
783 public boolean equals(Object obj) {
784 if (obj == null){
785 return false;
786 }
787 if (! (obj instanceof TimePeriod)){
788 return false;
789 }
790 TimePeriod that = (TimePeriod)obj;
791
792 if (! CdmUtils.nullSafeEqual(this.start, that.start)){
793 return false;
794 }
795 if (! CdmUtils.nullSafeEqual(this.end, that.end)){
796 return false;
797 }
798 if (! CdmUtils.nullSafeEqual(this.freeText, that.freeText)){
799 return false;
800 }
801 return true;
802 }
803
804 /* (non-Javadoc)
805 * @see java.lang.Object#hashCode()
806 */
807 @Override
808 public int hashCode() {
809 int hashCode = 7;
810 hashCode = 29*hashCode +
811 (start== null? 33: start.hashCode()) +
812 (end== null? 39: end.hashCode()) +
813 (freeText== null? 41: freeText.hashCode());
814 return super.hashCode();
815 }
816
817
818 //*********** CLONE **********************************/
819
820
821 /* (non-Javadoc)
822 * @see java.lang.Object#clone()
823 */
824 @Override
825 public Object clone() {
826 try {
827 TimePeriod result = (TimePeriod)super.clone();
828 result.setStart(this.start); //DateTime is immutable
829 result.setEnd(this.end);
830 result.setFreeText(this.freeText);
831 return result;
832 } catch (CloneNotSupportedException e) {
833 logger.warn("Clone not supported exception. Should never occurr !!");
834 return null;
835 }
836 }
837
838
839 }