Project

General

Profile

Download (18.7 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.lang.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
import org.joda.time.format.DateTimeFormatter;
38

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

    
41
import eu.etaxonomy.cdm.common.CdmUtils;
42
import eu.etaxonomy.cdm.hibernate.search.PartialBridge;
43
import eu.etaxonomy.cdm.jaxb.PartialAdapter;
44
import eu.etaxonomy.cdm.strategy.cache.common.TimePeriodPartialFormatter;
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 {
62
    private static final long serialVersionUID = 3405969418194981401L;
63
    private static final Logger logger = Logger.getLogger(TimePeriod.class);
64
    public static final DateTimeFieldType YEAR_TYPE = DateTimeFieldType.year();
65
    public static final DateTimeFieldType MONTH_TYPE = DateTimeFieldType.monthOfYear();
66
    public static final DateTimeFieldType DAY_TYPE = DateTimeFieldType.dayOfMonth();
67
    public static final DateTimeFieldType HOUR_TYPE = DateTimeFieldType.hourOfDay();
68
    public static final DateTimeFieldType MINUTE_TYPE = DateTimeFieldType.minuteOfHour();
69

    
70
    public static final Partial CONTINUED = new Partial
71
            (new DateTimeFieldType[]{YEAR_TYPE, MONTH_TYPE, DAY_TYPE},
72
             new int[]{9999, 11, 30});
73

    
74
    @XmlElement(name = "Start")
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 start;
81

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

    
90
    @XmlElement(name = "FreeText")
91
    private String freeText;
92

    
93
// ********************** FACTORY METHODS **************************/
94

    
95
    /**
96
     * Factory method
97
     * @return
98
     */
99
    public static final TimePeriod NewInstance(){
100
        return new TimePeriod();
101
    }
102

    
103

    
104
    /**
105
     * Factory method
106
     * @return
107
     */
108
    public static final TimePeriod NewInstance(Partial startDate){
109
        return new TimePeriod(startDate);
110
    }
111

    
112

    
113
    /**
114
     * Factory method
115
     * @return
116
     */
117
    public static final TimePeriod NewInstance(Partial startDate, Partial endDate){
118
        return new TimePeriod(startDate, endDate);
119
    }
120

    
121

    
122
    /**
123
     * Factory method
124
     * @return
125
     */
126
    public static final TimePeriod NewInstance(Integer year){
127
        Integer endYear = null;
128
        return NewInstance(year, endYear);
129
    }
130

    
131
    /**
132
     * Factory method
133
     * @return
134
     */
135
    public static final TimePeriod NewInstance(Integer startYear, Integer endYear){
136
        Partial startDate = null;
137
        Partial endDate = null;
138
        if (startYear != null){
139
            startDate = new Partial().with(YEAR_TYPE, startYear);
140
        }
141
        if (endYear != null){
142
            endDate = new Partial().with(YEAR_TYPE, endYear);
143
        }
144
        return new TimePeriod(startDate, endDate);
145
    }
146

    
147

    
148

    
149
    /**
150
     * Factory method to create a TimePeriod from a <code>Calendar</code>. The Calendar is stored as the starting instant.
151
     * @return
152
     */
153
    public static final TimePeriod NewInstance(Calendar startCalendar){
154
        return NewInstance(startCalendar, null);
155
    }
156

    
157
    /**
158
     * Factory method to create a TimePeriod from a <code>ReadableInstant</code>(e.g. <code>DateTime</code>).
159
     * The <code>ReadableInstant</code> is stored as the starting instant.
160
     * @return
161
     */
162
    public static final TimePeriod NewInstance(ReadableInstant readableInstant){
163
        return NewInstance(readableInstant, null);
164
    }
165

    
166
    /**
167
     * Factory method to create a TimePeriod from a starting and an ending <code>Calendar</code>
168
     * @return
169
     */
170
    public static final TimePeriod NewInstance(Calendar startCalendar, Calendar endCalendar){
171
        Partial startDate = null;
172
        Partial endDate = null;
173
        if (startCalendar != null){
174
            startDate = calendarToPartial(startCalendar);
175
        }
176
        if (endCalendar != null){
177
            endDate = calendarToPartial(endCalendar);
178
        }
179
        return new TimePeriod(startDate, endDate);
180
    }
181

    
182
    /**
183
     * Factory method to create a TimePeriod from a starting and an ending <code>Date</code>
184
     * @return TimePeriod
185
     */
186
    public static final TimePeriod NewInstance(Date startDate, Date endDate){
187
        //TODO conversion untested, implemented according to http://www.roseindia.net/java/java-conversion/datetocalender.shtml
188
        Calendar calStart = null;
189
        Calendar calEnd = null;
190
        if (startDate != null){
191
            calStart = Calendar.getInstance();
192
            calStart.setTime(startDate);
193
        }
194
        if (endDate != null){
195
            calEnd = Calendar.getInstance();
196
            calEnd.setTime(endDate);
197
        }
198
        return NewInstance(calStart, calEnd);
199
    }
200

    
201

    
202
    /**
203
     * Factory method to create a TimePeriod from a starting and an ending <code>ReadableInstant</code>(e.g. <code>DateTime</code>)
204
     * @return
205
     */
206
    public static final TimePeriod NewInstance(ReadableInstant startInstant, ReadableInstant endInstant){
207
        Partial startDate = null;
208
        Partial endDate = null;
209
        if (startInstant != null){
210
            startDate = readableInstantToPartial(startInstant);
211
        }
212
        if (endInstant != null){
213
            endDate = readableInstantToPartial(endInstant);
214
        }
215
        return new TimePeriod(startDate, endDate);
216
    }
217

    
218
//****************** CONVERTERS ******************/
219

    
220
    /**
221
     * Transforms a {@link Calendar} into a <code>Partial</code>
222
     * @param calendar
223
     * @return
224
     */
225
    public static Partial calendarToPartial(Calendar calendar){
226
        LocalDate ld = new LocalDate(calendar);
227
        Partial partial = new Partial(ld);
228
        return partial;
229
    }
230

    
231
    /**
232
     * Transforms a {@link ReadableInstant} into a <code>Partial</code>
233
     * @param calendar
234
     * @return
235
     */
236
    public static Partial readableInstantToPartial(ReadableInstant readableInstant){
237
        DateTime dt = readableInstant.toInstant().toDateTime();
238
        LocalDate ld = dt.toLocalDate();
239
        int hour = dt.hourOfDay().get();
240
        int minute = dt.minuteOfHour().get();
241
        Partial partial = new Partial(ld).with(HOUR_TYPE, hour).with(MINUTE_TYPE, minute);
242
        return partial;
243
    }
244

    
245

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

    
254

    
255
//****************** CONVERTERS ******************/
256

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

    
281

    
282

    
283
//*********************** CONSTRUCTOR *********************************/
284

    
285
    /**
286
     * Constructor
287
     */
288
    protected TimePeriod() {
289
        super();
290
    }
291
    public TimePeriod(Partial startDate) {
292
        start=startDate;
293
    }
294
    public TimePeriod(Partial startDate, Partial endDate) {
295
        start = startDate;
296
        end = endDate;
297
    }
298

    
299
//******************* GETTER / SETTER ************************************/
300

    
301

    
302
    @JsonIgnore // currently used for swagger model scanner
303
    public Partial getStart() {
304
        return start;
305
    }
306

    
307
    public void setStart(Partial start) {
308
        this.start = start;
309
    }
310

    
311

    
312
    @JsonIgnore // currently used for swagger model scanner
313
    public Partial getEnd() {
314
        return isContinued() ? null : end;
315
    }
316

    
317
    public void setEnd(Partial end) {
318
        this.end = end;
319
    }
320

    
321
    /**
322
     * For time periods that need to store more information than the one
323
     * that can be stored in <code>start</code> and <code>end</code>.
324
     * If free text is not <code>null</null> {@link #toString()} will always
325
     * return the free text value.
326
     * <BR>Use {@link #toString()} for public use.
327
     * @return the freeText
328
     */
329
    public String getFreeText() {
330
        return freeText;
331
    }
332

    
333

    
334
    /**
335
     * Use {@link #parseSingleDate(String)} for public use.
336
     * @param freeText the freeText to set
337
     */
338
    public void setFreeText(String freeText) {
339
        this.freeText = freeText;
340
    }
341

    
342

    
343
    /**
344
     * Returns the continued flag (internally stored as a constant
345
     * far away date. {@link #CONTINUED}
346
     * @return
347
     */
348
    public boolean isContinued() {
349
        return CONTINUED.equals(end);
350
    }
351
    /**
352
     * Sets the (virtual) continued flag.<BR><BR>
353
     * NOTE: setting the flag to true, will remove an
354
     * existing end date.
355
     * @param isContinued
356
     */
357
    public void setContinued(boolean isContinued) {
358
        if (isContinued == true){
359
            this.end = CONTINUED;
360
        }else if (isContinued()){
361
            this.end = null;
362
        }
363
    }
364

    
365

    
366
//******************* Transient METHODS ************************************/
367

    
368
    /**
369
     * True, if this time period represents a period not a single point in time.
370
     * This is by definition, that the time period has a start and an end value,
371
     * and both have a year value that is not null
372
     * @return
373
     */
374
    @Transient
375
    public boolean isPeriod(){
376
        if (getStartYear() != null && getEndYear() != null ){
377
            return true;
378
        }else{
379
            return false;
380
        }
381
    }
382

    
383
    /**
384
     * True, if there is no start date and no end date and no freetext representation exists.
385
     * @return
386
     */
387
    @Transient
388
    public boolean isEmpty(){
389
        if (StringUtils.isBlank(this.getFreeText()) && start == null  && end == null ){
390
            return true;
391
        }else{
392
            return false;
393
        }
394
    }
395

    
396

    
397
    @Transient
398
    public Integer getStartYear(){
399
        return getPartialValue(start, YEAR_TYPE);
400
    }
401

    
402
    @Transient
403
    public Integer getStartMonth(){
404
        return getPartialValue(start, MONTH_TYPE);
405
    }
406

    
407
    @Transient
408
    public Integer getStartDay(){
409
        return getPartialValue(start, DAY_TYPE);
410
    }
411

    
412
    @Transient
413
    public Integer getEndYear(){
414
        return getPartialValue(getEnd(), YEAR_TYPE);
415
    }
416

    
417
    @Transient
418
    public Integer getEndMonth(){
419
        return getPartialValue(getEnd(), MONTH_TYPE);
420
    }
421

    
422
    @Transient
423
    public Integer getEndDay(){
424
        return getPartialValue(getEnd(), DAY_TYPE);
425
    }
426

    
427
    public TimePeriod setStartYear(Integer year){
428
        return setStartField(year, YEAR_TYPE);
429
    }
430

    
431
    public TimePeriod setStartMonth(Integer month) throws IndexOutOfBoundsException{
432
        return setStartField(month, MONTH_TYPE);
433
    }
434

    
435
    public TimePeriod setStartDay(Integer day) throws IndexOutOfBoundsException{
436
        return setStartField(day, DAY_TYPE);
437
    }
438

    
439
    public TimePeriod setEndYear(Integer year){
440
        return setEndField(year, YEAR_TYPE);
441
    }
442

    
443
    public TimePeriod setEndMonth(Integer month) throws IndexOutOfBoundsException{
444
        return setEndField(month, MONTH_TYPE);
445
    }
446

    
447
    public TimePeriod setEndDay(Integer day) throws IndexOutOfBoundsException{
448
        return setEndField(day, DAY_TYPE);
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
    @Transient
465
    private TimePeriod setStartField(Integer value, DateTimeFieldType type)
466
            throws IndexOutOfBoundsException{
467
        start = setPartialField(start, value, type);
468
        return this;
469
    }
470

    
471
    @Transient
472
    private TimePeriod setEndField(Integer value, DateTimeFieldType type)
473
            throws IndexOutOfBoundsException{
474
        end = setPartialField(getEnd(), value, type);
475
        return this;
476
    }
477

    
478
// ******************************** internal methods *******************************/
479

    
480
    /**
481
     * Throws an IndexOutOfBoundsException if the value does not have a valid value
482
     * (e.g. month > 12, month < 1, day > 31, etc.)
483
     * @param value
484
     * @param type
485
     * @throws IndexOutOfBoundsException
486
     */
487
    private static void checkFieldValues(Integer value, DateTimeFieldType type, Partial partial)
488
            throws IndexOutOfBoundsException{
489
        int max = 9999999;
490
        if (type.equals(MONTH_TYPE)){
491
            max = 12;
492
        }
493
        if (type.equals(DAY_TYPE)){
494
            max = 31;
495
            Integer month = null;
496
            if (partial.isSupported(MONTH_TYPE)){
497
                month = partial.get(MONTH_TYPE);
498
            }
499
            if (month != null){
500
                if (month == 2){
501
                    max = 29;
502
                }else if (month == 4 ||month == 6 ||month == 9 ||month == 11){
503
                    max = 30;
504
                }
505
            }
506
        }
507
        if ( (value < 1 || value > max) ){
508
            throw new IndexOutOfBoundsException("Value must be between 1 and " +  max);
509
        }
510
    }
511

    
512

    
513
//**************************** to String ****************************************
514

    
515
    /**
516
     * Returns the {@link #getFreeText()} value if free text is not <code>null</code>.
517
     * Otherwise the concatenation of <code>start</code> and <code>end</code> is returned.
518
     *
519
     * @see java.lang.Object#toString()
520
     */
521
    @Override
522
    public String toString(){
523
        String result = null;
524
        if ( StringUtils.isNotBlank(this.getFreeText())){
525
            result = this.getFreeText();
526
        }else{
527
            result = getTimePeriod();
528
        }
529
        return result;
530
    }
531

    
532
    /**
533
     * Returns the concatenation of <code>start</code> and <code>end</code>
534
     */
535
    public String getTimePeriod(){
536
        String result = null;
537
        DateTimeFormatter formatter = TimePeriodPartialFormatter.NewInstance();
538
        String strStart = start != null ? start.toString(formatter): null;
539
        if (isContinued()){
540
            result = CdmUtils.concat("", strStart, "+");
541
        }else{
542
            String strEnd = end != null ? end.toString(formatter): null;
543
            result = CdmUtils.concat("-", strStart, strEnd);
544
        }
545

    
546
        return result;
547
    }
548

    
549
    @Transient
550
    public String getYear(){
551
        String result = "";
552
        if (getStartYear() != null){
553
            result += String.valueOf(getStartYear());
554
            if (getEndYear() != null){
555
                result += "-" + String.valueOf(getEndYear());
556
            }
557
        }else{
558
            if (getEndYear() != null){
559
                result += String.valueOf(getEndYear());
560
            }
561
        }
562
        if (isContinued()){
563
            result += "+";
564
        }
565
        return result;
566
    }
567

    
568
//*********** EQUALS **********************************/
569

    
570
    @Override
571
    public boolean equals(Object obj) {
572
        if (obj == null){
573
            return false;
574
        }
575
        if (! (obj instanceof TimePeriod)){
576
            return false;
577
        }
578
        TimePeriod that = (TimePeriod)obj;
579

    
580
        if (! CdmUtils.nullSafeEqual(this.start, that.start)){
581
            return false;
582
        }
583
        if (! CdmUtils.nullSafeEqual(this.end, that.end)){
584
            return false;
585
        }
586
        if (! CdmUtils.nullSafeEqual(this.freeText, that.freeText)){
587
            return false;
588
        }
589
        //see comment in verbatimTimePeriod#equals
590
        String thisVerbatimDate = (this instanceof VerbatimTimePeriod)?
591
                ((VerbatimTimePeriod)this).getVerbatimDate():null;
592
        String thatVerbatimDate = (obj instanceof VerbatimTimePeriod)?
593
                ((VerbatimTimePeriod)obj).getVerbatimDate():null;
594
        if (! CdmUtils.nullSafeEqual(thisVerbatimDate, thatVerbatimDate)){
595
            return false;
596
        }
597
        return true;
598
    }
599

    
600
    @Override
601
    public int hashCode() {
602
        int hashCode = 7;
603
        hashCode = 29*hashCode +
604
                    (start == null? 33: start.hashCode()) +
605
                    (end == null? 39: end.hashCode()) +
606
                    (freeText == null? 41: freeText.hashCode());
607
        return hashCode;
608
    }
609

    
610

    
611
//*********** CLONE **********************************/
612

    
613
    @Override
614
    public Object clone()  {
615
        try {
616
            TimePeriod result = (TimePeriod)super.clone();
617
            copyCloned(this, result);
618
            return result;
619
        } catch (CloneNotSupportedException e) {
620
            logger.warn("Clone not supported exception. Should never occurr !!");
621
            return null;
622
        }
623
    }
624

    
625

    
626
    /**
627
     * @param result
628
     */
629
    protected static void copyCloned(TimePeriod origin, TimePeriod target) {
630
        target.setStart(origin.start);   //DateTime is immutable
631
        target.setEnd(origin.end);
632
        target.setFreeText(origin.freeText);
633
    }
634

    
635
}
(71-71/80)