Project

General

Profile

Download (19.4 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.Date;
15

    
16
import javax.persistence.Embeddable;
17
import javax.persistence.MappedSuperclass;
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.commons.lang3.StringUtils;
27
import org.apache.log4j.Logger;
28
import org.hibernate.annotations.Type;
29
import org.hibernate.search.annotations.Analyze;
30
import org.hibernate.search.annotations.Field;
31
import org.hibernate.search.annotations.FieldBridge;
32
import org.joda.time.DateTime;
33
import org.joda.time.DateTimeFieldType;
34
import org.joda.time.LocalDate;
35
import org.joda.time.Partial;
36
import org.joda.time.ReadableInstant;
37

    
38
import com.fasterxml.jackson.annotation.JsonIgnore;
39

    
40
import eu.etaxonomy.cdm.common.CdmUtils;
41
import eu.etaxonomy.cdm.common.UTF8;
42
import eu.etaxonomy.cdm.format.common.TimePeriodFormatter;
43
import eu.etaxonomy.cdm.hibernate.search.PartialBridge;
44
import eu.etaxonomy.cdm.jaxb.PartialAdapter;
45

    
46
/**
47
 * @author m.doering
48
 * @since 08-Nov-2007 13:07:00
49
 * @updated 05-Dec-2008 23:00:05
50
 * @updated 14-Jul-2013 move parser methods to TimePeriodParser
51
 */
52
@XmlAccessorType(XmlAccessType.FIELD)
53
@XmlType(name = "TimePeriod", propOrder = {
54
    "start",
55
    "end",
56
    "freeText"
57
})
58
@XmlRootElement(name = "TimePeriod")
59
@Embeddable
60
@MappedSuperclass
61
public class TimePeriod implements Cloneable, Serializable, ICheckEmpty {
62

    
63
    private static final long serialVersionUID = 3405969418194981401L;
64
    private static final Logger logger = Logger.getLogger(TimePeriod.class);
65

    
66
    public static final DateTimeFieldType YEAR_TYPE = DateTimeFieldType.year();
67
    public static final DateTimeFieldType MONTH_TYPE = DateTimeFieldType.monthOfYear();
68
    public static final DateTimeFieldType DAY_TYPE = DateTimeFieldType.dayOfMonth();
69
    public static final DateTimeFieldType HOUR_TYPE = DateTimeFieldType.hourOfDay();
70
    public static final DateTimeFieldType MINUTE_TYPE = DateTimeFieldType.minuteOfHour();
71

    
72
    public static final String SEP = UTF8.EN_DASH.toString(); //maybe this will be moved to a formatter class in future
73

    
74
    public static final Partial CONTINUED = new Partial
75
            (new DateTimeFieldType[]{YEAR_TYPE, MONTH_TYPE, DAY_TYPE},
76
             new int[]{9999, 11, 30});
77

    
78
    private static TimePeriodFormatter formatter = TimePeriodFormatter.NewDefaultInstance();
79

    
80
    @XmlElement(name = "Start")
81
    @XmlJavaTypeAdapter(value = PartialAdapter.class)
82
    @Type(type="partialUserType")
83
    @Field(analyze = Analyze.NO)
84
    @FieldBridge(impl = PartialBridge.class)
85
    @JsonIgnore // currently used for swagger model scanner
86
    private Partial start;
87

    
88
    @XmlElement(name = "End")
89
    @XmlJavaTypeAdapter(value = PartialAdapter.class)
90
    @Type(type="partialUserType")
91
    @Field(analyze = Analyze.NO)
92
    @FieldBridge(impl = PartialBridge.class)
93
    @JsonIgnore // currently used for swagger model scanner
94
    private Partial end;
95

    
96
    @XmlElement(name = "FreeText")
97
    private String freeText;
98

    
99
// ********************** FACTORY METHODS **************************/
100

    
101
    public static final TimePeriod NewInstance(){
102
        return new TimePeriod();
103
    }
104

    
105
    public static final TimePeriod NewInstance(Partial startDate){
106
        return new TimePeriod(startDate, null, null);
107
    }
108

    
109
    public static final TimePeriod NewInstance(Partial startDate, Partial endDate){
110
        return new TimePeriod(startDate, endDate, null);
111
    }
112

    
113
    public static final TimePeriod NewInstance(Integer year){
114
        Integer endYear = null;
115
        return NewInstance(year, endYear);
116
    }
117

    
118
    public static final TimePeriod NewInstance(Integer startYear, Integer endYear){
119
        return new TimePeriod(yearToPartial(startYear), yearToPartial(endYear), null);
120
    }
121

    
122
    /**
123
     * Factory method to create a TimePeriod from a <code>Calendar</code>. The Calendar is stored as the starting instant.
124
     * @return
125
     */
126
    public static final TimePeriod NewInstance(Calendar startCalendar){
127
        return NewInstance(startCalendar, null);
128
    }
129

    
130
    /**
131
     * Factory method to create a TimePeriod from a <code>ReadableInstant</code>(e.g. <code>DateTime</code>).
132
     * The <code>ReadableInstant</code> is stored as the starting instant.
133
     * @return
134
     */
135
    public static final TimePeriod NewInstance(ReadableInstant readableInstant){
136
        return NewInstance(readableInstant, null);
137
    }
138

    
139
    /**
140
     * Factory method to create a TimePeriod from a starting and an ending <code>Calendar</code>
141
     * @return
142
     */
143
    public static final TimePeriod NewInstance(Calendar startCalendar, Calendar endCalendar){
144
        return new TimePeriod(calendarToPartial(startCalendar), calendarToPartial(endCalendar), null);
145
    }
146

    
147
    /**
148
     * Factory method to create a TimePeriod from a starting and an ending <code>Date</code>
149
     * @return TimePeriod
150
     */
151
    public static final TimePeriod NewInstance(Date startDate, Date endDate){
152
        return NewInstance(dateToPartial(startDate), dateToPartial(endDate));
153
    }
154

    
155
    /**
156
     * Factory method to create a TimePeriod from a starting and an ending <code>ReadableInstant</code>(e.g. <code>DateTime</code>)
157
     * @return
158
     */
159
    public static final TimePeriod NewInstance(ReadableInstant startInstant, ReadableInstant endInstant){
160
        return new TimePeriod(readableInstantToPartial(startInstant), readableInstantToPartial(endInstant), null);
161
    }
162

    
163
//****************** PARTIAL CONVERTERS ******************/
164

    
165
    /**
166
     * Transforms a {@link Calendar} into a <code>Partial</code>
167
     * @param calendar
168
     * @return
169
     */
170
    public static Partial calendarToPartial(Calendar calendar){
171
        if (calendar == null){
172
            return null;
173
        }else{
174
            LocalDate ld = new LocalDate(calendar);
175
            Partial partial = new Partial(ld);
176
            return partial;
177
        }
178
    }
179

    
180
    /**
181
     * Transforms a {@link ReadableInstant} into a <code>Partial</code>
182
     */
183
    public static Partial readableInstantToPartial(ReadableInstant readableInstant){
184
        if (readableInstant == null){
185
            return null;
186
        }else{
187
            DateTime dt = readableInstant.toInstant().toDateTime();
188
            LocalDate ld = dt.toLocalDate();
189
            int hour = dt.hourOfDay().get();
190
            int minute = dt.minuteOfHour().get();
191
            Partial partial = new Partial(ld).with(HOUR_TYPE, hour).with(MINUTE_TYPE, minute);
192
            return partial;
193
        }
194
    }
195

    
196
    /**
197
     * Transforms a {@link Date} into a <code>Partial</code>.
198
     */
199
    public static Partial dateToPartial(Date date){
200
        //TODO conversion untested, implemented according to http://www.roseindia.net/java/java-conversion/datetocalender.shtml
201
        if (date != null){
202
            Calendar cal = Calendar.getInstance();
203
            cal.setTime(date);
204
            return calendarToPartial(cal);
205
        }else{
206
            return null;
207
        }
208
    }
209

    
210
    /**
211
     * Transforms an Integer into a <code>Partial</code> with the Integer value
212
     * being the year of the Partial.
213
     */
214
    public static Partial yearToPartial(Integer year){
215
        if (year != null){
216
            return new Partial().with(YEAR_TYPE, year);
217
        }else{
218
            return null;
219
        }
220
    }
221
    public static Partial monthToPartial(Integer month){
222
        if (month != null){
223
            return new Partial().with(MONTH_TYPE, month);
224
        }else{
225
            return null;
226
        }
227
    }
228
    public static Partial monthAndDayToPartial(Integer month, Integer day){
229
        if (month != null || day != null){
230
            Partial result = new Partial();
231
            if (month != null){
232
                result = result.with(MONTH_TYPE, month);
233
            }
234
            if (day != null){
235
                result = result.with(DAY_TYPE, day);
236
            }
237
            return result;
238
        }else{
239
            return null;
240
        }
241
    }
242

    
243
    public static Integer getPartialValue(Partial partial, DateTimeFieldType type){
244
        if (partial == null || ! partial.isSupported(type)){
245
            return null;
246
        }else{
247
            return partial.get(type);
248
        }
249
    }
250

    
251
//****************** TIME PERIOD CONVERTERS ******************/
252

    
253
    public static TimePeriod fromVerbatim(VerbatimTimePeriod verbatimTimePeriod){
254
        if (verbatimTimePeriod == null){
255
            return null;
256
        }
257
        TimePeriod result = TimePeriod.NewInstance();
258
        copyCloned(verbatimTimePeriod, result);
259
        if (StringUtils.isNotBlank(verbatimTimePeriod.getVerbatimDate()) &&
260
              StringUtils.isBlank(result.getFreeText())){
261
          result.setFreeText(verbatimTimePeriod.toString());
262
        }
263
        return result;
264
    }
265
    public static VerbatimTimePeriod toVerbatim(TimePeriod timePeriod){
266
        if (timePeriod == null){
267
            return null;
268
        }else if (timePeriod instanceof VerbatimTimePeriod){
269
            return (VerbatimTimePeriod)timePeriod;
270
        }else{
271
            VerbatimTimePeriod result = VerbatimTimePeriod.NewVerbatimInstance();
272
            copyCloned(timePeriod, result);
273
            return result;
274
        }
275
    }
276
    public VerbatimTimePeriod toVerbatim(){
277
        return toVerbatim(this);
278
    }
279

    
280
//*********************** CONSTRUCTOR *********************************/
281

    
282
    protected TimePeriod() {
283
        super();
284
    }
285
    protected TimePeriod(Partial startDate, Partial endDate, String freeText) {
286
        this.start = startDate;
287
        this.end = endDate;
288
        this.freeText = freeText;
289
    }
290

    
291
//******************* GETTER / SETTER ************************************/
292

    
293

    
294
    @JsonIgnore // currently used for swagger model scanner
295
    public Partial getStart() {
296
        return start;
297
    }
298

    
299
    public void setStart(Partial start) {
300
        this.start = start;
301
    }
302

    
303

    
304
    @JsonIgnore // currently used for swagger model scanner
305
    public Partial getEnd() {
306
        return isContinued() ? null : end;
307
    }
308

    
309
    public void setEnd(Partial end) {
310
        this.end = end;
311
    }
312

    
313
    /**
314
     * For time periods that need to store more information than the one
315
     * that can be stored in <code>start</code> and <code>end</code>.
316
     * If free text is not <code>null</null> {@link #toString()} will always
317
     * return the free text value.
318
     * <BR>Use {@link #toString()} for public use.
319
     * @return the freeText
320
     */
321
    public String getFreeText() {
322
        return freeText;
323
    }
324
    /**
325
     * Use {@link #parseSingleDate(String)} for public use.
326
     * @param freeText the freeText to set
327
     */
328
    public void setFreeText(String freeText) {
329
        this.freeText = freeText;
330
    }
331

    
332
    /**
333
     * Returns the continued flag (internally stored as a constant
334
     * far away date. {@link #CONTINUED}
335
     * @return
336
     */
337
    public boolean isContinued() {
338
        return CONTINUED.equals(end);
339
    }
340
    /**
341
     * Sets the (virtual) continued flag.<BR><BR>
342
     * NOTE: setting the flag to true, will remove an
343
     * existing end date.
344
     * @param isContinued
345
     */
346
    public void setContinued(boolean isContinued) {
347
        if (isContinued == true){
348
            this.end = CONTINUED;
349
        }else if (isContinued()){
350
            this.end = null;
351
        }
352
    }
353

    
354
//******************* Transient METHODS ************************************/
355

    
356
    /**
357
     * True, if this time period represents a period not a single point in time.
358
     * This is by definition, that the time period has a start and an end value,
359
     * and both have a year value that is not null
360
     * @return
361
     */
362
    @Transient
363
    public boolean isPeriod(){
364
        if (getStartYear() != null && getEndYear() != null ){
365
            return true;
366
        }else{
367
            return false;
368
        }
369
    }
370

    
371
    /**
372
     * True, if there is no start date, no end date and no freetext representation.
373
     */
374
    @Transient
375
    public boolean isEmpty(){
376
        if (StringUtils.isBlank(this.getFreeText()) && isEmpty(start) && isEmpty(end)){
377
            return true;
378
        }else{
379
            return false;
380
        }
381
    }
382

    
383
    @Transient
384
    public Integer getStartYear(){
385
        return getPartialValue(start, YEAR_TYPE);
386
    }
387

    
388
    @Transient
389
    public Integer getStartMonth(){
390
        return getPartialValue(start, MONTH_TYPE);
391
    }
392

    
393
    @Transient
394
    public Integer getStartDay(){
395
        return getPartialValue(start, DAY_TYPE);
396
    }
397

    
398
    @Transient
399
    public Integer getEndYear(){
400
        return getPartialValue(getEnd(), YEAR_TYPE);
401
    }
402

    
403
    @Transient
404
    public Integer getEndMonth(){
405
        return getPartialValue(getEnd(), MONTH_TYPE);
406
    }
407

    
408
    @Transient
409
    public Integer getEndDay(){
410
        return getPartialValue(getEnd(), DAY_TYPE);
411
    }
412

    
413
    public TimePeriod setStartYear(Integer year){
414
        return setStartField(year, YEAR_TYPE);
415
    }
416

    
417
    public TimePeriod setStartMonth(Integer month) throws IndexOutOfBoundsException{
418
        return setStartField(month, MONTH_TYPE);
419
    }
420

    
421
    public TimePeriod setStartDay(Integer day) throws IndexOutOfBoundsException{
422
        return setStartField(day, DAY_TYPE);
423
    }
424

    
425
    public TimePeriod setEndYear(Integer year){
426
        return setEndField(year, YEAR_TYPE);
427
    }
428

    
429
    public TimePeriod setEndMonth(Integer month) throws IndexOutOfBoundsException{
430
        return setEndField(month, MONTH_TYPE);
431
    }
432

    
433
    public TimePeriod setEndDay(Integer day) throws IndexOutOfBoundsException{
434
        return setEndField(day, DAY_TYPE);
435
    }
436

    
437
    @Transient
438
    private TimePeriod setStartField(Integer value, DateTimeFieldType type)
439
            throws IndexOutOfBoundsException{
440
        start = setPartialField(start, value, type);
441
        return this;
442
    }
443

    
444
    @Transient
445
    private TimePeriod setEndField(Integer value, DateTimeFieldType type)
446
            throws IndexOutOfBoundsException{
447
        end = setPartialField(getEnd(), value, type);
448
        return this;
449
    }
450

    
451
    public static Partial setPartialField(Partial partial, Integer value, DateTimeFieldType type)
452
            throws IndexOutOfBoundsException{
453
        if (partial == null){
454
            partial = new Partial();
455
        }
456
        if (value == null){
457
            return partial.without(type);
458
        }else{
459
            checkFieldValues(value, type, partial);
460
            return partial.with(type, value);
461
        }
462
    }
463

    
464

    
465
// ******************************** internal methods *******************************/
466

    
467
    /**
468
     * Throws an IndexOutOfBoundsException if the value does not have a valid value
469
     * (e.g. month > 12, month < 1, day > 31, etc.)
470
     * @param value
471
     * @param type
472
     * @throws IndexOutOfBoundsException
473
     */
474
    private static void checkFieldValues(Integer value, DateTimeFieldType type, Partial partial)
475
            throws IndexOutOfBoundsException{
476
        int max = 9999999;
477
        if (type.equals(MONTH_TYPE)){
478
            max = 12;
479
        }
480
        if (type.equals(DAY_TYPE)){
481
            max = 31;
482
            Integer month = null;
483
            if (partial.isSupported(MONTH_TYPE)){
484
                month = partial.get(MONTH_TYPE);
485
            }
486
            if (month != null){
487
                if (month == 2){
488
                    max = 29;
489
                }else if (month == 4 ||month == 6 ||month == 9 ||month == 11){
490
                    max = 30;
491
                }
492
            }
493
        }
494
        if ( (value < 1 || value > max) ){
495
            throw new IndexOutOfBoundsException("Value must be between 1 and " +  max);
496
        }
497
    }
498

    
499
//**************************** to String ****************************************
500

    
501
    /**
502
     * Returns the {@link #getFreeText()} value if free text is not <code>null</code>.
503
     * Otherwise the concatenation of <code>start</code> and <code>end</code> is returned.
504
     *
505
     * @see java.lang.Object#toString()
506
     */
507
    @Override
508
    public String toString(){
509
        return formatter.format(this);
510
    }
511

    
512
    /**
513
     * Returns the concatenation of <code>start</code> and <code>end</code>
514
     */
515
    public String getTimePeriod(){
516
        return formatter.getTimePeriod(this);
517
    }
518

    
519
    @Transient
520
    public String getYear(){
521
        return formatter.getYear(this);
522
    }
523

    
524
    @Override
525
    public boolean checkEmpty() {
526
        //TODO unify isEmpty && checkEmpty
527
        return isEmpty();
528
    }
529

    
530
    protected boolean isBlank(String str) {
531
        return StringUtils.isBlank(str);
532
    }
533

    
534
    protected boolean isEmpty(Partial partial) {
535
        return partial == null? true : partial.getFields().length == 0;
536
    }
537

    
538
//*********** EQUALS **********************************/
539

    
540
    @Override
541
    public boolean equals(Object obj) {
542
        if (obj == null){
543
            return false;
544
        }
545
        if (! (obj instanceof TimePeriod)){
546
            return false;
547
        }
548
        TimePeriod that = (TimePeriod)obj;
549

    
550
        if (! CdmUtils.nullSafeEqual(this.start, that.start)){
551
            return false;
552
        }
553
        if (! CdmUtils.nullSafeEqual(this.end, that.end)){
554
            return false;
555
        }
556
        if (! CdmUtils.nullSafeEqual(this.freeText, that.freeText)){
557
            return false;
558
        }
559
        //see comment in VerbatimTimePeriod#equals
560
        String thisVerbatimDate = (this instanceof VerbatimTimePeriod)?
561
                ((VerbatimTimePeriod)this).getVerbatimDate():null;
562
        String thatVerbatimDate = (obj instanceof VerbatimTimePeriod)?
563
                ((VerbatimTimePeriod)obj).getVerbatimDate():null;
564
        if (! CdmUtils.nullSafeEqual(thisVerbatimDate, thatVerbatimDate)){
565
            return false;
566
        }
567
        //see comment in ExtendedTimePeriod#equals
568
        Partial thisExtremeStart = (this instanceof ExtendedTimePeriod)?
569
                ((ExtendedTimePeriod)this).getExtremeStart():null;
570
        Partial thatExtremeStart = (obj instanceof ExtendedTimePeriod)?
571
                ((ExtendedTimePeriod)obj).getExtremeStart():null;
572
        if (! CdmUtils.nullSafeEqual(thisExtremeStart, thatExtremeStart)){
573
            return false;
574
        }
575

    
576
        Partial thisExtremeEnd = (this instanceof ExtendedTimePeriod)?
577
                ((ExtendedTimePeriod)this).getExtremeEnd():null;
578
        Partial thatExtremeEnd = (obj instanceof ExtendedTimePeriod)?
579
                ((ExtendedTimePeriod)obj).getExtremeEnd():null;
580
        if (! CdmUtils.nullSafeEqual(thisExtremeEnd, thatExtremeEnd)){
581
            return false;
582
        }
583

    
584
        return true;
585
    }
586

    
587
    @Override
588
    public int hashCode() {
589
        int hashCode = 7;
590
        hashCode = 29*hashCode +
591
                    (start == null? 33: start.hashCode()) +
592
                    (end == null? 39: end.hashCode()) +
593
                    (freeText == null? 41: freeText.hashCode());
594
        return hashCode;
595
    }
596

    
597
//*********** CLONE **********************************/
598

    
599
    @Override
600
    public TimePeriod clone()  {
601
        try {
602
            TimePeriod result = (TimePeriod)super.clone();
603
            copyCloned(this, result);
604
            return result;
605
        } catch (CloneNotSupportedException e) {
606
            logger.warn("Clone not supported exception. Should never occurr !!");
607
            return null;
608
        }
609
    }
610

    
611
    protected static void copyCloned(TimePeriod origin, TimePeriod target) {
612
        target.setStart(origin.start);   //DateTime is immutable
613
        target.setEnd(origin.end);
614
        target.setFreeText(origin.freeText);
615
    }
616

    
617
}
(53-53/58)