2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
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.
10 package eu
.etaxonomy
.cdm
.model
.common
;
12 import java
.io
.Serializable
;
13 import java
.util
.Calendar
;
14 import java
.util
.Date
;
16 import javax
.persistence
.Embeddable
;
17 import javax
.persistence
.Transient
;
18 import javax
.xml
.bind
.annotation
.XmlAccessType
;
19 import javax
.xml
.bind
.annotation
.XmlAccessorType
;
20 import javax
.xml
.bind
.annotation
.XmlElement
;
21 import javax
.xml
.bind
.annotation
.XmlRootElement
;
22 import javax
.xml
.bind
.annotation
.XmlType
;
23 import javax
.xml
.bind
.annotation
.adapters
.XmlJavaTypeAdapter
;
25 import org
.apache
.commons
.lang
.StringUtils
;
26 import org
.apache
.log4j
.Logger
;
27 import org
.hibernate
.annotations
.Type
;
28 import org
.hibernate
.search
.annotations
.Analyze
;
29 import org
.hibernate
.search
.annotations
.Field
;
30 import org
.hibernate
.search
.annotations
.FieldBridge
;
31 import org
.joda
.time
.DateTime
;
32 import org
.joda
.time
.DateTimeFieldType
;
33 import org
.joda
.time
.LocalDate
;
34 import org
.joda
.time
.Partial
;
35 import org
.joda
.time
.ReadableInstant
;
36 import org
.joda
.time
.format
.DateTimeFormatter
;
38 import com
.fasterxml
.jackson
.annotation
.JsonIgnore
;
40 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
41 import eu
.etaxonomy
.cdm
.hibernate
.search
.PartialBridge
;
42 import eu
.etaxonomy
.cdm
.jaxb
.PartialAdapter
;
43 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.TimePeriodPartialFormatter
;
47 * @created 08-Nov-2007 13:07:00
48 * @updated 05-Dec-2008 23:00:05
49 * @updated 14-Jul-2013 move parser methods to TimePeriodParser
51 @XmlAccessorType(XmlAccessType
.FIELD
)
52 @XmlType(name
= "TimePeriod", propOrder
= {
57 @XmlRootElement(name
= "TimePeriod")
59 public class TimePeriod
implements Cloneable
, Serializable
{
60 private static final long serialVersionUID
= 3405969418194981401L;
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();
66 @XmlElement(name
= "Start")
67 @XmlJavaTypeAdapter(value
= PartialAdapter
.class)
68 @Type(type
="partialUserType")
69 @Field(analyze
= Analyze
.NO
)
70 @FieldBridge(impl
= PartialBridge
.class)
71 @JsonIgnore // currently used for swagger model scanner
72 private Partial start
;
74 @XmlElement(name
= "End")
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
83 @XmlElement(name
= "FreeText")
84 private String freeText
;
86 // ********************** FACTORY METHODS **************************/
92 public static TimePeriod
NewInstance(){
93 return new TimePeriod();
101 public static TimePeriod
NewInstance(Partial startDate
){
102 return new TimePeriod(startDate
);
110 public static TimePeriod
NewInstance(Partial startDate
, Partial endDate
){
111 return new TimePeriod(startDate
, endDate
);
119 public static TimePeriod
NewInstance(Integer year
){
120 Integer endYear
= null;
121 return NewInstance(year
, endYear
);
128 public static TimePeriod
NewInstance(Integer startYear
, Integer endYear
){
129 Partial startDate
= null;
130 Partial endDate
= null;
131 if (startYear
!= null){
132 startDate
= new Partial().with(YEAR_TYPE
, startYear
);
134 if (endYear
!= null){
135 endDate
= new Partial().with(YEAR_TYPE
, endYear
);
137 return new TimePeriod(startDate
, endDate
);
143 * Factory method to create a TimePeriod from a <code>Calendar</code>. The Calendar is stored as the starting instant.
146 public static TimePeriod
NewInstance(Calendar startCalendar
){
147 return NewInstance(startCalendar
, null);
151 * Factory method to create a TimePeriod from a <code>ReadableInstant</code>(e.g. <code>DateTime</code>).
152 * The <code>ReadableInstant</code> is stored as the starting instant.
155 public static TimePeriod
NewInstance(ReadableInstant readableInstant
){
156 return NewInstance(readableInstant
, null);
160 * Factory method to create a TimePeriod from a starting and an ending <code>Calendar</code>
163 public static TimePeriod
NewInstance(Calendar startCalendar
, Calendar endCalendar
){
164 Partial startDate
= null;
165 Partial endDate
= null;
166 if (startCalendar
!= null){
167 startDate
= calendarToPartial(startCalendar
);
169 if (endCalendar
!= null){
170 endDate
= calendarToPartial(endCalendar
);
172 return new TimePeriod(startDate
, endDate
);
176 * Factory method to create a TimePeriod from a starting and an ending <code>Date</code>
179 public static TimePeriod
NewInstance(Date startDate
, Date endDate
){
180 //TODO conversion untested, implemented according to http://www.roseindia.net/java/java-conversion/datetocalender.shtml
181 Calendar calStart
= null;
182 Calendar calEnd
= null;
183 if (startDate
!= null){
184 calStart
= Calendar
.getInstance();
185 calStart
.setTime(startDate
);
187 if (endDate
!= null){
188 calEnd
= Calendar
.getInstance();
189 calEnd
.setTime(endDate
);
191 return NewInstance(calStart
, calEnd
);
196 * Factory method to create a TimePeriod from a starting and an ending <code>ReadableInstant</code>(e.g. <code>DateTime</code>)
199 public static TimePeriod
NewInstance(ReadableInstant startInstant
, ReadableInstant endInstant
){
200 Partial startDate
= null;
201 Partial endDate
= null;
202 if (startInstant
!= null){
203 startDate
= readableInstantToPartial(startInstant
);
205 if (endInstant
!= null){
206 endDate
= readableInstantToPartial(endInstant
);
208 return new TimePeriod(startDate
, endDate
);
211 //****************** CONVERTERS ******************/
214 * Transforms a {@link Calendar} into a <code>Partial</code>
218 public static Partial
calendarToPartial(Calendar calendar
){
219 LocalDate ld
= new LocalDate(calendar
);
220 Partial partial
= new Partial(ld
);
225 * Transforms a {@link ReadableInstant} into a <code>Partial</code>
229 public static Partial
readableInstantToPartial(ReadableInstant readableInstant
){
230 DateTime dt
= readableInstant
.toInstant().toDateTime();
231 LocalDate ld
= dt
.toLocalDate();
232 Partial partial
= new Partial(ld
);
237 public static Integer
getPartialValue(Partial partial
, DateTimeFieldType type
){
238 if (partial
== null || ! partial
.isSupported(type
)){
241 return partial
.get(type
);
247 //*********************** CONSTRUCTOR *********************************/
252 protected TimePeriod() {
255 public TimePeriod(Partial startDate
) {
258 public TimePeriod(Partial startDate
, Partial endDate
) {
263 //******************* GETTER / SETTER ************************************/
266 @JsonIgnore // currently used for swagger model scanner
267 public Partial
getStart() {
271 public void setStart(Partial start
) {
276 @JsonIgnore // currently used for swagger model scanner
277 public Partial
getEnd() {
281 public void setEnd(Partial end
) {
286 * For time periods that need to store more information than the one
287 * that can be stored in <code>start</code> and <code>end</code>.
288 * If free text is not <code>null</null> {@link #toString()} will always
289 * return the free text value.
290 * <BR>Use {@link #toString()} for public use.
291 * @return the freeText
293 public String
getFreeText() {
299 * Use {@link #parseSingleDate(String)} for public use.
300 * @param freeText the freeText to set
302 public void setFreeText(String freeText
) {
303 this.freeText
= freeText
;
307 //******************* Transient METHODS ************************************/
310 * True, if this time period represents a period not a single point in time.
311 * This is by definition, that the time period has a start and an end value,
312 * and both have a year value that is not null
316 public boolean isPeriod(){
317 if (getStartYear() != null && getEndYear() != null ){
325 * True, if there is no start date and no end date and no freetext representation exists.
329 public boolean isEmpty(){
330 if (StringUtils
.isBlank(this.getFreeText()) && start
== null && end
== null ){
340 public String
getYear(){
342 if (getStartYear() != null){
343 result
+= String
.valueOf(getStartYear());
344 if (getEndYear() != null){
345 result
+= "-" + String
.valueOf(getEndYear());
348 if (getEndYear() != null){
349 result
+= String
.valueOf(getEndYear());
356 public Integer
getStartYear(){
357 return getPartialValue(start
, YEAR_TYPE
);
361 public Integer
getStartMonth(){
362 return getPartialValue(start
, MONTH_TYPE
);
366 public Integer
getStartDay(){
367 return getPartialValue(start
, DAY_TYPE
);
371 public Integer
getEndYear(){
372 return getPartialValue(end
, YEAR_TYPE
);
376 public Integer
getEndMonth(){
377 return getPartialValue(end
, MONTH_TYPE
);
381 public Integer
getEndDay(){
382 return getPartialValue(end
, DAY_TYPE
);
385 public TimePeriod
setStartYear(Integer year
){
386 return setStartField(year
, YEAR_TYPE
);
389 public TimePeriod
setStartMonth(Integer month
) throws IndexOutOfBoundsException
{
390 return setStartField(month
, MONTH_TYPE
);
393 public TimePeriod
setStartDay(Integer day
) throws IndexOutOfBoundsException
{
394 return setStartField(day
, DAY_TYPE
);
397 public TimePeriod
setEndYear(Integer year
){
398 return setEndField(year
, YEAR_TYPE
);
401 public TimePeriod
setEndMonth(Integer month
) throws IndexOutOfBoundsException
{
402 return setEndField(month
, MONTH_TYPE
);
405 public TimePeriod
setEndDay(Integer day
) throws IndexOutOfBoundsException
{
406 return setEndField(day
, DAY_TYPE
);
409 public static Partial
setPartialField(Partial partial
, Integer value
, DateTimeFieldType type
)
410 throws IndexOutOfBoundsException
{
411 if (partial
== null){
412 partial
= new Partial();
415 return partial
.without(type
);
417 checkFieldValues(value
, type
, partial
);
418 return partial
.with(type
, value
);
423 private TimePeriod
setStartField(Integer value
, DateTimeFieldType type
)
424 throws IndexOutOfBoundsException
{
425 start
= setPartialField(start
, value
, type
);
430 private TimePeriod
setEndField(Integer value
, DateTimeFieldType type
)
431 throws IndexOutOfBoundsException
{
432 end
= setPartialField(end
, value
, type
);
436 // ******************************** internal methods *******************************/
439 * Throws an IndexOutOfBoundsException if the value does not have a valid value
440 * (e.g. month > 12, month < 1, day > 31, etc.)
443 * @throws IndexOutOfBoundsException
445 private static void checkFieldValues(Integer value
, DateTimeFieldType type
, Partial partial
)
446 throws IndexOutOfBoundsException
{
448 if (type
.equals(MONTH_TYPE
)){
451 if (type
.equals(DAY_TYPE
)){
453 Integer month
= null;
454 if (partial
.isSupported(MONTH_TYPE
)){
455 month
= partial
.get(MONTH_TYPE
);
460 }else if (month
== 4 ||month
== 6 ||month
== 9 ||month
== 11){
465 if ( (value
< 1 || value
> max
) ){
466 throw new IndexOutOfBoundsException("Value must be between 1 and " + max
);
470 private void initStart(){
472 start
= new Partial();
476 private void initEnd(){
483 //**************************** to String ****************************************
486 * Returns the {@link #getFreeText()} value if free text is not <code>null</code>.
487 * Otherwise the concatenation of <code>start</code> and <code>end</code> is returned.
489 * @see java.lang.Object#toString()
492 public String
toString(){
493 String result
= null;
494 DateTimeFormatter formatter
= TimePeriodPartialFormatter
.NewInstance();
495 if ( StringUtils
.isNotBlank(this.getFreeText())){
496 result
= this.getFreeText();
498 String strStart
= start
!= null ? start
.toString(formatter
): null;
499 String strEnd
= end
!= null ? end
.toString(formatter
): null;
500 result
= CdmUtils
.concat("-", strStart
, strEnd
);
505 //*********** EQUALS **********************************/
509 * @see java.lang.Object#equals(java.lang.Object)
512 public boolean equals(Object obj
) {
516 if (! (obj
instanceof TimePeriod
)){
519 TimePeriod that
= (TimePeriod
)obj
;
521 if (! CdmUtils
.nullSafeEqual(this.start
, that
.start
)){
524 if (! CdmUtils
.nullSafeEqual(this.end
, that
.end
)){
527 if (! CdmUtils
.nullSafeEqual(this.freeText
, that
.freeText
)){
534 * @see java.lang.Object#hashCode()
537 public int hashCode() {
539 hashCode
= 29*hashCode
+
540 (start
== null?
33: start
.hashCode()) +
541 (end
== null?
39: end
.hashCode()) +
542 (freeText
== null?
41: freeText
.hashCode());
543 return super.hashCode();
547 //*********** CLONE **********************************/
551 * @see java.lang.Object#clone()
554 public Object
clone() {
556 TimePeriod result
= (TimePeriod
)super.clone();
557 result
.setStart(this.start
); //DateTime is immutable
558 result
.setEnd(this.end
);
559 result
.setFreeText(this.freeText
);
561 } catch (CloneNotSupportedException e
) {
562 logger
.warn("Clone not supported exception. Should never occurr !!");