Project

General

Profile

Download (20.1 KB) Statistics
| Branch: | Tag: | Revision:
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.regex.Matcher;
15
import java.util.regex.Pattern;
16

    
17
import javax.persistence.Embeddable;
18
import javax.persistence.Transient;
19
import javax.xml.bind.annotation.XmlAccessType;
20
import javax.xml.bind.annotation.XmlAccessorType;
21
import javax.xml.bind.annotation.XmlElement;
22
import javax.xml.bind.annotation.XmlRootElement;
23
import javax.xml.bind.annotation.XmlType;
24
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
25

    
26
import org.apache.log4j.Logger;
27
import org.hibernate.annotations.Type;
28
import org.hibernate.search.annotations.Field;
29
import org.hibernate.search.annotations.FieldBridge;
30
import org.joda.time.DateTime;
31
import org.joda.time.DateTimeFieldType;
32
import org.joda.time.LocalDate;
33
import org.joda.time.Partial;
34
import org.joda.time.ReadableInstant;
35
import org.joda.time.ReadablePartial;
36
import org.joda.time.format.DateTimeFormatter;
37

    
38
import eu.etaxonomy.cdm.common.CdmUtils;
39
import eu.etaxonomy.cdm.hibernate.PartialBridge;
40
import eu.etaxonomy.cdm.jaxb.PartialAdapter;
41

    
42
/**
43
 * @author m.doering
44
 * @version 1.0
45
 * @created 08-Nov-2007 13:07:00
46
 * @updated 05-Dec-2008 23:00:05
47
 */
48
@XmlAccessorType(XmlAccessType.FIELD)
49
@XmlType(name = "TimePeriod", propOrder = {
50
    "start",
51
    "end",
52
    "freeText"
53
})
54
@XmlRootElement(name = "TimePeriod")
55
@Embeddable
56
public class TimePeriod implements Cloneable, Serializable {
57
	private static final Logger logger = Logger.getLogger(TimePeriod.class);
58
	public static final DateTimeFieldType MONTH_TYPE = DateTimeFieldType.monthOfYear();
59
	public static final DateTimeFieldType YEAR_TYPE = DateTimeFieldType.year();
60
	public static final DateTimeFieldType DAY_TYPE = DateTimeFieldType.dayOfMonth();
61
	
62
	@XmlElement(name = "Start")
63
	@XmlJavaTypeAdapter(value = PartialAdapter.class)
64
	@Type(type="partialUserType")
65
	@Field(index = org.hibernate.search.annotations.Index.UN_TOKENIZED)
66
	@FieldBridge(impl = PartialBridge.class)
67
	private Partial start;
68
	
69
	@XmlElement(name = "End")
70
	@XmlJavaTypeAdapter(value = PartialAdapter.class)
71
	@Type(type="partialUserType")
72
	@Field(index = org.hibernate.search.annotations.Index.UN_TOKENIZED)
73
	@FieldBridge(impl = PartialBridge.class)
74
	private Partial end;
75

    
76
	
77
	@XmlElement(name = "FreeText")
78
	private String freeText;
79
	
80
	
81
	/**
82
	 * Factory method
83
	 * @return
84
	 */
85
	public static TimePeriod NewInstance(){
86
		return new TimePeriod();
87
	}
88
	
89
	
90
	/**
91
	 * Factory method
92
	 * @return
93
	 */
94
	public static TimePeriod NewInstance(Partial startDate){
95
		return new TimePeriod(startDate);
96
	}
97
	
98
	
99
	/**
100
	 * Factory method
101
	 * @return
102
	 */
103
	public static TimePeriod NewInstance(Partial startDate, Partial endDate){
104
		return new TimePeriod(startDate, endDate);
105
	}
106
	
107
	
108
	/**
109
	 * Factory method
110
	 * @return
111
	 */
112
	public static TimePeriod NewInstance(Integer year){
113
		Integer endYear = null;
114
		return NewInstance(year, endYear);
115
	}
116
	
117
	/**
118
	 * Factory method
119
	 * @return
120
	 */
121
	public static TimePeriod NewInstance(Integer startYear, Integer endYear){
122
		Partial startDate = null;
123
		Partial endDate = null;
124
		if (startYear != null){
125
			startDate = new Partial().with(YEAR_TYPE, startYear);
126
		}
127
		if (endYear != null){
128
			endDate = new Partial().with(YEAR_TYPE, endYear);
129
		}
130
		return new TimePeriod(startDate, endDate);
131
	}
132

    
133
	
134
	
135
	/**
136
	 * Factory method to create a TimePeriod from a <code>Calendar</code>. The Calendar is stored as the starting instant.   
137
	 * @return
138
	 */
139
	public static TimePeriod NewInstance(Calendar startCalendar){
140
		return NewInstance(startCalendar, null);
141
	}
142

    
143
	/**
144
	 * Factory method to create a TimePeriod from a <code>ReadableInstant</code>(e.g. <code>DateTime</code>).
145
	 * The <code>ReadableInstant</code> is stored as the starting instant.   
146
	 * @return
147
	 */
148
	public static TimePeriod NewInstance(ReadableInstant readableInstant){
149
		return NewInstance(readableInstant, null);
150
	}
151
	
152
	/**
153
	 * Factory method to create a TimePeriod from a starting and an ending <code>Calendar</code>   
154
	 * @return
155
	 */
156
	public static TimePeriod NewInstance(Calendar startCalendar, Calendar endCalendar){
157
		Partial startDate = null;
158
		Partial endDate = null;
159
		if (startCalendar != null){
160
			startDate = calendarToPartial(startCalendar);
161
		}
162
		if (endCalendar != null){
163
			endDate = calendarToPartial(endCalendar);
164
		}
165
		return new TimePeriod(startDate, endDate);
166
	}
167

    
168
	
169
	/**
170
	 * Factory method to create a TimePeriod from a starting and an ending <code>ReadableInstant</code>(e.g. <code>DateTime</code>)   
171
	 * @return
172
	 */
173
	public static TimePeriod NewInstance(ReadableInstant startInstant, ReadableInstant endInstant){
174
		Partial startDate = null;
175
		Partial endDate = null;
176
		if (startInstant != null){
177
			startDate = readableInstantToPartial(startInstant);
178
		}
179
		if (endInstant != null){
180
			endDate = readableInstantToPartial(endInstant);
181
		}
182
		return new TimePeriod(startDate, endDate);
183
	}
184

    
185
	
186
	/**
187
	 * Transforms a <code>Calendar</code> into a <code>Partial</code>
188
	 * @param calendar
189
	 * @return
190
	 */
191
	public static Partial calendarToPartial(Calendar calendar){
192
		LocalDate ld = new LocalDate(calendar);
193
		Partial partial = new Partial(ld);
194
		return partial;
195
	}
196
	
197
	/**
198
	 * Transforms a <code>Calendar</code> into a <code>Partial</code>
199
	 * @param calendar
200
	 * @return
201
	 */
202
	public static Partial readableInstantToPartial(ReadableInstant readableInstant){
203
		DateTime dt = readableInstant.toInstant().toDateTime();
204
		LocalDate ld = dt.toLocalDate();
205
		Partial partial = new Partial(ld);
206
		return partial;
207
	}
208
	
209
	/**
210
	 * Constructor
211
	 */
212
	protected TimePeriod() {
213
		super();
214
	}
215
	public TimePeriod(Partial startDate) {
216
		start=startDate;
217
	}
218
	public TimePeriod(Partial startDate, Partial endDate) {
219
		start=startDate;
220
		end=endDate;
221
	}
222

    
223
	/**
224
	 * True, if this time period represents a period not a single point in time.
225
	 * This is by definition, that the time period has a start and an end value,
226
	 * and both have a year value that is not null
227
	 * @return
228
	 */
229
	@Transient
230
	public boolean isPeriod(){
231
		if (getStartYear() != null && getEndYear() != null ){
232
			return true;
233
		}else{
234
			return false;
235
		}
236
	}
237
	
238
	/**
239
	 * True, if there is no start date and no end date and no freetext representation exists.
240
	 * @return
241
	 */
242
	@Transient
243
	public boolean isEmpty(){
244
		if (CdmUtils.isEmpty(this.getFreeText()) && start == null  && end == null ){
245
			return true;
246
		}else{
247
			return false;
248
		}
249
	}
250
	
251
	
252
	public Partial getStart() {
253
		return start;
254
	}
255
	
256
	public void setStart(Partial start) {
257
		this.start = start;
258
	}
259
	
260
	public Partial getEnd() {
261
		return end;
262
	}
263
	
264
	public void setEnd(Partial end) {
265
		this.end = end;
266
	}
267
	
268
	/**
269
	 * For time periods that need to store more information than the one
270
	 * that can be stored in <code>start</code> and <code>end</code>.
271
	 * If free text is not <code>null</null> {@link #toString()} will always
272
	 * return the free text value.
273
	 * <BR>Use {@link #toString()} for public use.
274
	 * @return the freeText
275
	 */
276
	public String getFreeText() {
277
		return freeText;
278
	}
279

    
280

    
281
	/**
282
	 * Use {@link #parseSingleDate(String)} for public use.
283
	 * @param freeText the freeText to set
284
	 */
285
	public void setFreeText(String freeText) {
286
		this.freeText = freeText;
287
	}
288

    
289

    
290
	@Transient
291
	public String getYear(){
292
		String result = "";
293
		if (getStartYear() != null){
294
			result += String.valueOf(getStartYear());
295
			if (getEndYear() != null){
296
				result += "-" + String.valueOf(getEndYear());
297
			}
298
		}else{
299
			if (getEndYear() != null){
300
				result += String.valueOf(getEndYear());
301
			}
302
		}
303
		return result;
304
	}
305
	
306
	@Transient
307
	public Integer getStartYear(){
308
		return getPartialValue(start, YEAR_TYPE);
309
	}
310
	
311
	@Transient
312
	public Integer getStartMonth(){
313
		return getPartialValue(start, MONTH_TYPE);
314
	}
315

    
316
	@Transient
317
	public Integer getStartDay(){
318
		return getPartialValue(start, DAY_TYPE);
319
	}
320

    
321
	@Transient
322
	public Integer getEndYear(){
323
		return getPartialValue(end, YEAR_TYPE);
324
	}
325

    
326
	@Transient
327
	public Integer getEndMonth(){
328
		return getPartialValue(end, MONTH_TYPE);
329
	}
330

    
331
	@Transient
332
	public Integer getEndDay(){
333
		return getPartialValue(end, DAY_TYPE);
334
	}
335
	
336
	public static Integer getPartialValue(Partial partial, DateTimeFieldType type){
337
		if (partial == null || ! partial.isSupported(type)){
338
			return null;
339
		}else{
340
			return partial.get(type);
341
		}
342
		
343
	}
344
	
345
	public TimePeriod setStartYear(Integer year){
346
		return setStartField(year, YEAR_TYPE);
347
	}
348
	
349
	public TimePeriod setStartMonth(Integer month) throws IndexOutOfBoundsException{
350
		return setStartField(month, MONTH_TYPE);
351
	}
352

    
353
	public TimePeriod setStartDay(Integer day) throws IndexOutOfBoundsException{
354
		return setStartField(day, DAY_TYPE);
355
	}
356
	
357
	public TimePeriod setEndYear(Integer year){
358
		return setEndField(year, YEAR_TYPE);
359
	}
360

    
361
	public TimePeriod setEndMonth(Integer month) throws IndexOutOfBoundsException{
362
		return setEndField(month, MONTH_TYPE);
363
	}
364

    
365
	public TimePeriod setEndDay(Integer day) throws IndexOutOfBoundsException{
366
		return setEndField(day, DAY_TYPE);
367
	}
368
	
369
	public static Partial setPartialField(Partial partial, Integer value, DateTimeFieldType type) 
370
			throws IndexOutOfBoundsException{
371
		if (partial == null){
372
			partial = new Partial();
373
		}
374
		if (value == null){
375
			return partial.without(type);
376
		}else{
377
			checkFieldValues(value, type, partial);
378
			return partial.with(type, value);
379
		}
380
	}
381
	
382
	private TimePeriod setStartField(Integer value, DateTimeFieldType type) 
383
			throws IndexOutOfBoundsException{
384
		start = setPartialField(start, value, type);
385
		return this;
386
	}
387

    
388
	private TimePeriod setEndField(Integer value, DateTimeFieldType type)
389
			throws IndexOutOfBoundsException{
390
		end = setPartialField(end, value, type);
391
		return this;
392
	}
393
	
394
	/**
395
	 * Throws an IndexOutOfBoundsException if the value does not have a valid value
396
	 * (e.g. month > 12, month < 1, day > 31, etc.)
397
	 * @param value
398
	 * @param type
399
	 * @throws IndexOutOfBoundsException
400
	 */
401
	private static void checkFieldValues(Integer value, DateTimeFieldType type, Partial partial)
402
			throws IndexOutOfBoundsException{
403
		int max = 9999999;
404
		if (type.equals(MONTH_TYPE)){
405
			max = 12;
406
		}
407
		if (type.equals(DAY_TYPE)){
408
			max = 31;
409
			Integer month = null;
410
			if (partial.isSupported(MONTH_TYPE)){
411
				month = partial.get(MONTH_TYPE);
412
			}
413
			if (month != null){
414
				if (month == 2){
415
					max = 29;
416
				}else if (month == 4 ||month == 6 ||month == 9 ||month == 11){
417
					max = 30; 
418
				}
419
			}
420
		}
421
		if ( (value < 1 || value > max) ){
422
			throw new IndexOutOfBoundsException("Value must be between 1 and " +  max);
423
		}
424
	}
425
	
426
	private void initStart(){
427
		if (start == null){
428
			start = new Partial();
429
		}
430
	}
431
	
432
	private void initEnd(){
433
		if (end == null){
434
			end = new Partial();
435
		}
436
	}
437
	
438
	
439
	//patter for first year in string;
440
	private static final Pattern firstYearPattern =  Pattern.compile("\\d{4}");
441
	//case "1806"[1807];
442
	private static final Pattern uncorrectYearPatter =  Pattern.compile("\"\\d{4}\"\\s*\\[\\d{4}\\]");
443
	//case fl. 1806 or c. 1806 or fl. 1806?
444
	private static final Pattern prefixedYearPattern =  Pattern.compile("(fl|c)\\.\\s*\\d{4}(\\s*-\\s*\\d{4})?\\??");
445
	//standard
446
	private static final Pattern standardPattern =  Pattern.compile("\\s*\\d{2,4}(\\s*-(\\s*\\d{2,4})?)?");
447
	private static final String strDotDate = "[0-3]?\\d\\.[01]?\\d\\.\\d{4,4}";
448
	private static final String strDotDatePeriodPattern = String.format("%s(\\s*-\\s*%s?)?", strDotDate, strDotDate);
449
	private static final Pattern dotDatePattern =  Pattern.compile(strDotDatePeriodPattern);
450
	
451
	
452
	public static TimePeriod parseString(TimePeriod timePeriod, String periodString){
453
		//TODO move to parser class
454
		//TODO until now only quick and dirty (and partly wrong)
455
		TimePeriod result = timePeriod;
456
		
457
		if(timePeriod == null){
458
			return timePeriod;
459
		}
460
		
461
		if (periodString == null){
462
			return result;
463
		}
464
		periodString = periodString.trim();
465
		
466
		result.setFreeText(null);
467
		
468
		//case "1806"[1807];
469
		if (uncorrectYearPatter.matcher(periodString).matches()){
470
			result.setFreeText(periodString);
471
			String realYear = periodString.split("\\[")[1];
472
			realYear = realYear.replace("]", "");
473
			result.setStartYear(Integer.valueOf(realYear));
474
			result.setFreeText(periodString);
475
		//case fl. 1806 or c. 1806 or fl. 1806?
476
		}else if(prefixedYearPattern.matcher(periodString).matches()){
477
			result.setFreeText(periodString);
478
			Matcher yearMatcher = firstYearPattern.matcher(periodString);
479
			yearMatcher.find();
480
			String startYear = yearMatcher.group();
481
			result.setStartYear(Integer.valueOf(startYear));
482
			if (yearMatcher.find()){
483
				String endYear = yearMatcher.group();
484
				result.setEndYear(Integer.valueOf(endYear));
485
			}
486
		}else if (dotDatePattern.matcher(periodString).matches()){
487
			parseDotDatePattern(periodString, result);
488
		}else if (standardPattern.matcher(periodString).matches()){
489
			parseStandardPattern(periodString, result);
490
		}else{
491
			result.setFreeText(periodString);
492
		}
493
		return result;
494
	}
495

    
496
	/**
497
	 * @param periodString
498
	 * @param result
499
	 */
500
	private static void parseDotDatePattern(String periodString,TimePeriod result) {
501
		String[] dates = periodString.split("-");
502
		Partial dtStart = null;
503
		Partial dtEnd = null;
504
		
505
		if (dates.length > 2 || dates.length <= 0){
506
			logger.warn("More than 1 '-' in period String: " + periodString);
507
			result.setFreeText(periodString);
508
		}else {
509
			try {
510
				//start
511
				if (! CdmUtils.isEmpty(dates[0])){
512
					dtStart = parseSingleDotDate(dates[0].trim());
513
				}
514
				
515
				//end
516
				if (dates.length >= 2 && ! CdmUtils.isEmpty(dates[1])){
517
					dtEnd = parseSingleDotDate(dates[1].trim());
518
				}
519
				
520
				result.setStart(dtStart);
521
				result.setEnd(dtEnd);
522
			} catch (IllegalArgumentException e) {
523
				//logger.warn(e.getMessage());
524
				result.setFreeText(periodString);
525
			}
526
		}
527
	}
528
	
529
	
530
	/**
531
	 * @param periodString
532
	 * @param result
533
	 */
534
	private static void parseStandardPattern(String periodString,
535
			TimePeriod result) {
536
		String[] years = periodString.split("-");
537
		Partial dtStart = null;
538
		Partial dtEnd = null;
539
		
540
		if (years.length > 2 || years.length <= 0){
541
			logger.warn("More than 1 '-' in period String: " + periodString);
542
		}else {
543
			try {
544
				//start
545
				if (! CdmUtils.isEmpty(years[0])){
546
					dtStart = parseSingleDate(years[0].trim());
547
				}
548
				
549
				//end
550
				if (years.length >= 2 && ! CdmUtils.isEmpty(years[1])){
551
					years[1] = years[1].trim();
552
					if (years[1].length()==2 && dtStart != null && dtStart.isSupported(DateTimeFieldType.year())){
553
						years[1] = String.valueOf(dtStart.get(DateTimeFieldType.year())/100) + years[1];
554
					}
555
					dtEnd = parseSingleDate(years[1]);
556
				}
557
				
558
				result.setStart(dtStart);
559
				result.setEnd(dtEnd);
560
			} catch (IllegalArgumentException e) {
561
				//logger.warn(e.getMessage());
562
				result.setFreeText(periodString);
563
			}
564
		}
565
	}
566
	
567
	public static TimePeriod parseString(String strPeriod) {
568
		TimePeriod timePeriod = TimePeriod.NewInstance();
569
		return parseString(timePeriod, strPeriod);
570
	}
571
	
572
	
573
	protected static Partial parseSingleDate(String singleDateString) throws IllegalArgumentException{
574
		//FIXME until now only quick and dirty and incomplete
575
		Partial partial =  new Partial();
576
		singleDateString = singleDateString.trim();
577
		if (CdmUtils.isNumeric(singleDateString)){
578
			try {
579
				Integer year = Integer.valueOf(singleDateString.trim());
580
				if (year < 1000 && year > 2100){
581
					logger.warn("Not a valid year: " + year + ". Year must be between 1000 and 2100");
582
				}else if (year < 1700 && year > 2100){
583
					logger.warn("Not a valid taxonomic year: " + year + ". Year must be between 1750 and 2100");
584
					partial = partial.with(YEAR_TYPE, year);
585
				}else{
586
					partial = partial.with(YEAR_TYPE, year);
587
				}
588
			} catch (NumberFormatException e) {
589
				logger.debug("Not a Integer format in getCalendar()");
590
				throw new IllegalArgumentException(e);
591
			}
592
		}else{
593
			throw new IllegalArgumentException("Until now only years can be parsed as single dates. But date is: " + singleDateString);
594
		}
595
		return partial;
596

    
597
	}
598
	
599
	protected static Partial parseSingleDotDate(String singleDateString) throws IllegalArgumentException{
600
		Partial partial =  new Partial();
601
		singleDateString = singleDateString.trim();
602
		String[] split = singleDateString.split("\\.");
603
		int length = split.length;
604
		if (length > 3){
605
			throw new IllegalArgumentException(String.format("More than 2 dots in date '%s'", singleDateString));
606
		}
607
		String strYear = split[split.length-1];
608
		String strMonth = length >= 2? split[split.length-2]: null;
609
		String strDay = length >= 3? split[split.length-3]: null;
610
		
611
		
612
		try {
613
			Integer year = Integer.valueOf(strYear.trim());
614
			Integer month = Integer.valueOf(strMonth.trim());
615
			Integer day = Integer.valueOf(strDay.trim());
616
			if (year < 1000 && year > 2100){
617
				logger.warn("Not a valid year: " + year + ". Year must be between 1000 and 2100");
618
			}else if (year < 1700 && year > 2100){
619
				logger.warn("Not a valid taxonomic year: " + year + ". Year must be between 1750 and 2100");
620
				partial = partial.with(YEAR_TYPE, year);
621
			}else{
622
				partial = partial.with(YEAR_TYPE, year);
623
			}
624
			if (month != null && month != 0){
625
				partial = partial.with(MONTH_TYPE, month);
626
			}
627
			if (day != null && day != 0){
628
				partial = partial.with(DAY_TYPE, day);
629
			}
630
		} catch (NumberFormatException e) {
631
			logger.debug("Not a Integer format somewhere in " + singleDateString);
632
			throw new IllegalArgumentException(e);
633
		}
634
		return partial;
635

    
636
	}
637
	
638
	
639
	
640
	private class TimePeriodPartialFormatter extends DateTimeFormatter{
641
		private TimePeriodPartialFormatter(){
642
			super(null, null);
643
		}
644
		public String print(ReadablePartial partial){
645
			//TODO
646
			String result = "";
647
			String year = (partial.isSupported(YEAR_TYPE))? String.valueOf(partial.get(YEAR_TYPE)):null;
648
			String month = (partial.isSupported(MONTH_TYPE))? String.valueOf(partial.get(MONTH_TYPE)):null;;
649
			String day = (partial.isSupported(DAY_TYPE))? String.valueOf(partial.get(DAY_TYPE)):null;;
650
			
651
			if (month !=null){
652
				if (year == null){
653
					year = "xxxx";
654
				}
655
			}
656
			if (day != null){
657
				if (month == null){
658
					month = "xx";
659
				}
660
				if (year == null){
661
					year = "xxxx";
662
				}
663
			}
664
			result = (day != null)? day + "." : "";
665
			result += (month != null)? month + "." : "";
666
			result += (year != null)? year : "";
667
			
668
			return result;
669
		}
670
		
671
	}
672
	
673
//**************************** to String ****************************************	
674
	
675
	/** 
676
	 * Returns the {@link #getFreeText()} value if free text is not <code>null</code>.
677
	 * Otherwise the concatenation of <code>start</code> and <code>end</code> is returned. 
678
	 * 
679
	 * @see java.lang.Object#toString()
680
	 */
681
	public String toString(){
682
		String result = null;
683
		DateTimeFormatter formatter = new TimePeriodPartialFormatter();
684
		if ( CdmUtils.isNotEmpty(this.getFreeText())){
685
			result = this.getFreeText();
686
		}else{
687
			String strStart = start != null ? start.toString(formatter): null;
688
			String strEnd = end != null ? end.toString(formatter): null;
689
			result = CdmUtils.concat("-", strStart, strEnd);
690
		}
691
		return result;
692
	}
693
	
694
//*********** EQUALS **********************************/	
695
	
696

    
697
	/* (non-Javadoc)
698
	 * @see java.lang.Object#equals(java.lang.Object)
699
	 */
700
	@Override
701
	public boolean equals(Object obj) {
702
		if (obj == null){
703
			return false;
704
		}
705
		if (! (obj instanceof TimePeriod)){
706
			return false;
707
		}
708
		TimePeriod that = (TimePeriod)obj;
709
		
710
		if (! CdmUtils.nullSafeEqual(this.start, that.start)){
711
			return false;
712
		}
713
		if (! CdmUtils.nullSafeEqual(this.end, that.end)){
714
			return false;
715
		}
716
		if (! CdmUtils.nullSafeEqual(this.freeText, that.freeText)){
717
			return false;
718
		}
719
		return true;
720
	}
721
	
722
	/* (non-Javadoc)
723
	 * @see java.lang.Object#hashCode()
724
	 */
725
	@Override
726
	public int hashCode() {
727
		int hashCode = 7;
728
		hashCode = 29*hashCode +  
729
					(start== null? 33: start.hashCode()) + 
730
					(end== null? 39: end.hashCode()) + 
731
					(freeText== null? 41: freeText.hashCode()); 
732
		return super.hashCode();
733
	}	
734
	
735
	
736
//*********** CLONE **********************************/	
737
	
738

    
739
	/* (non-Javadoc)
740
	 * @see java.lang.Object#clone()
741
	 */
742
	@Override
743
	public Object clone()  {
744
		try {
745
			TimePeriod result = (TimePeriod)super.clone();
746
			result.setStart(this.start);   //DateTime is immutable
747
			result.setEnd(this.end);	
748
			result.setFreeText(this.freeText);
749
			return result;
750
		} catch (CloneNotSupportedException e) {
751
			logger.warn("Clone not supported exception. Should never occurr !!");
752
			return null;
753
		}
754
	}
755

    
756
	
757
}
(56-56/63)