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 eu
.etaxonomy
.cdm
.common
.CdmUtils
;
39 import eu
.etaxonomy
.cdm
.hibernate
.search
.PartialBridge
;
40 import eu
.etaxonomy
.cdm
.jaxb
.PartialAdapter
;
41 import eu
.etaxonomy
.cdm
.strategy
.cache
.common
.TimePeriodPartialFormatter
;
45 * @created 08-Nov-2007 13:07:00
46 * @updated 05-Dec-2008 23:00:05
47 * @updated 14-Jul-2013 move parser methods to TimePeriodParser
49 @XmlAccessorType(XmlAccessType
.FIELD
)
50 @XmlType(name
= "TimePeriod", propOrder
= {
55 @XmlRootElement(name
= "TimePeriod")
57 public class TimePeriod
implements Cloneable
, Serializable
{
58 private static final long serialVersionUID
= 3405969418194981401L;
59 private static final Logger logger
= Logger
.getLogger(TimePeriod
.class);
60 public static final DateTimeFieldType MONTH_TYPE
= DateTimeFieldType
.monthOfYear();
61 public static final DateTimeFieldType YEAR_TYPE
= DateTimeFieldType
.year();
62 public static final DateTimeFieldType DAY_TYPE
= DateTimeFieldType
.dayOfMonth();
64 @XmlElement(name
= "Start")
65 @XmlJavaTypeAdapter(value
= PartialAdapter
.class)
66 @Type(type
="partialUserType")
67 @Field(analyze
= Analyze
.NO
)
68 @FieldBridge(impl
= PartialBridge
.class)
69 private Partial start
;
71 @XmlElement(name
= "End")
72 @XmlJavaTypeAdapter(value
= PartialAdapter
.class)
73 @Type(type
="partialUserType")
74 @Field(analyze
= Analyze
.NO
)
75 @FieldBridge(impl
= PartialBridge
.class)
79 @XmlElement(name
= "FreeText")
80 private String freeText
;
82 // ********************** FACTORY METHODS **************************/
88 public static TimePeriod
NewInstance(){
89 return new TimePeriod();
97 public static TimePeriod
NewInstance(Partial startDate
){
98 return new TimePeriod(startDate
);
106 public static TimePeriod
NewInstance(Partial startDate
, Partial endDate
){
107 return new TimePeriod(startDate
, endDate
);
115 public static TimePeriod
NewInstance(Integer year
){
116 Integer endYear
= null;
117 return NewInstance(year
, endYear
);
124 public static TimePeriod
NewInstance(Integer startYear
, Integer endYear
){
125 Partial startDate
= null;
126 Partial endDate
= null;
127 if (startYear
!= null){
128 startDate
= new Partial().with(YEAR_TYPE
, startYear
);
130 if (endYear
!= null){
131 endDate
= new Partial().with(YEAR_TYPE
, endYear
);
133 return new TimePeriod(startDate
, endDate
);
139 * Factory method to create a TimePeriod from a <code>Calendar</code>. The Calendar is stored as the starting instant.
142 public static TimePeriod
NewInstance(Calendar startCalendar
){
143 return NewInstance(startCalendar
, null);
147 * Factory method to create a TimePeriod from a <code>ReadableInstant</code>(e.g. <code>DateTime</code>).
148 * The <code>ReadableInstant</code> is stored as the starting instant.
151 public static TimePeriod
NewInstance(ReadableInstant readableInstant
){
152 return NewInstance(readableInstant
, null);
156 * Factory method to create a TimePeriod from a starting and an ending <code>Calendar</code>
159 public static TimePeriod
NewInstance(Calendar startCalendar
, Calendar endCalendar
){
160 Partial startDate
= null;
161 Partial endDate
= null;
162 if (startCalendar
!= null){
163 startDate
= calendarToPartial(startCalendar
);
165 if (endCalendar
!= null){
166 endDate
= calendarToPartial(endCalendar
);
168 return new TimePeriod(startDate
, endDate
);
172 * Factory method to create a TimePeriod from a starting and an ending <code>Date</code>
175 public static TimePeriod
NewInstance(Date startDate
, Date endDate
){
176 //TODO conversion untested, implemented according to http://www.roseindia.net/java/java-conversion/datetocalender.shtml
177 Calendar calStart
= null;
178 Calendar calEnd
= null;
179 if (startDate
!= null){
180 calStart
= Calendar
.getInstance();
181 calStart
.setTime(startDate
);
183 if (endDate
!= null){
184 calEnd
= Calendar
.getInstance();
185 calEnd
.setTime(endDate
);
187 return NewInstance(calStart
, calEnd
);
192 * Factory method to create a TimePeriod from a starting and an ending <code>ReadableInstant</code>(e.g. <code>DateTime</code>)
195 public static TimePeriod
NewInstance(ReadableInstant startInstant
, ReadableInstant endInstant
){
196 Partial startDate
= null;
197 Partial endDate
= null;
198 if (startInstant
!= null){
199 startDate
= readableInstantToPartial(startInstant
);
201 if (endInstant
!= null){
202 endDate
= readableInstantToPartial(endInstant
);
204 return new TimePeriod(startDate
, endDate
);
207 //****************** CONVERTERS ******************/
210 * Transforms a {@link Calendar} into a <code>Partial</code>
214 public static Partial
calendarToPartial(Calendar calendar
){
215 LocalDate ld
= new LocalDate(calendar
);
216 Partial partial
= new Partial(ld
);
221 * Transforms a {@link ReadableInstant} into a <code>Partial</code>
225 public static Partial
readableInstantToPartial(ReadableInstant readableInstant
){
226 DateTime dt
= readableInstant
.toInstant().toDateTime();
227 LocalDate ld
= dt
.toLocalDate();
228 Partial partial
= new Partial(ld
);
233 public static Integer
getPartialValue(Partial partial
, DateTimeFieldType type
){
234 if (partial
== null || ! partial
.isSupported(type
)){
237 return partial
.get(type
);
243 //*********************** CONSTRUCTOR *********************************/
248 protected TimePeriod() {
251 public TimePeriod(Partial startDate
) {
254 public TimePeriod(Partial startDate
, Partial endDate
) {
259 //******************* GETTER / SETTER ************************************/
261 public Partial
getStart() {
265 public void setStart(Partial start
) {
269 public Partial
getEnd() {
273 public void setEnd(Partial end
) {
278 * For time periods that need to store more information than the one
279 * that can be stored in <code>start</code> and <code>end</code>.
280 * If free text is not <code>null</null> {@link #toString()} will always
281 * return the free text value.
282 * <BR>Use {@link #toString()} for public use.
283 * @return the freeText
285 public String
getFreeText() {
291 * Use {@link #parseSingleDate(String)} for public use.
292 * @param freeText the freeText to set
294 public void setFreeText(String freeText
) {
295 this.freeText
= freeText
;
299 //******************* Transient METHODS ************************************/
302 * True, if this time period represents a period not a single point in time.
303 * This is by definition, that the time period has a start and an end value,
304 * and both have a year value that is not null
308 public boolean isPeriod(){
309 if (getStartYear() != null && getEndYear() != null ){
317 * True, if there is no start date and no end date and no freetext representation exists.
321 public boolean isEmpty(){
322 if (StringUtils
.isBlank(this.getFreeText()) && start
== null && end
== null ){
332 public String
getYear(){
334 if (getStartYear() != null){
335 result
+= String
.valueOf(getStartYear());
336 if (getEndYear() != null){
337 result
+= "-" + String
.valueOf(getEndYear());
340 if (getEndYear() != null){
341 result
+= String
.valueOf(getEndYear());
348 public Integer
getStartYear(){
349 return getPartialValue(start
, YEAR_TYPE
);
353 public Integer
getStartMonth(){
354 return getPartialValue(start
, MONTH_TYPE
);
358 public Integer
getStartDay(){
359 return getPartialValue(start
, DAY_TYPE
);
363 public Integer
getEndYear(){
364 return getPartialValue(end
, YEAR_TYPE
);
368 public Integer
getEndMonth(){
369 return getPartialValue(end
, MONTH_TYPE
);
373 public Integer
getEndDay(){
374 return getPartialValue(end
, DAY_TYPE
);
377 public TimePeriod
setStartYear(Integer year
){
378 return setStartField(year
, YEAR_TYPE
);
381 public TimePeriod
setStartMonth(Integer month
) throws IndexOutOfBoundsException
{
382 return setStartField(month
, MONTH_TYPE
);
385 public TimePeriod
setStartDay(Integer day
) throws IndexOutOfBoundsException
{
386 return setStartField(day
, DAY_TYPE
);
389 public TimePeriod
setEndYear(Integer year
){
390 return setEndField(year
, YEAR_TYPE
);
393 public TimePeriod
setEndMonth(Integer month
) throws IndexOutOfBoundsException
{
394 return setEndField(month
, MONTH_TYPE
);
397 public TimePeriod
setEndDay(Integer day
) throws IndexOutOfBoundsException
{
398 return setEndField(day
, DAY_TYPE
);
401 public static Partial
setPartialField(Partial partial
, Integer value
, DateTimeFieldType type
)
402 throws IndexOutOfBoundsException
{
403 if (partial
== null){
404 partial
= new Partial();
407 return partial
.without(type
);
409 checkFieldValues(value
, type
, partial
);
410 return partial
.with(type
, value
);
415 private TimePeriod
setStartField(Integer value
, DateTimeFieldType type
)
416 throws IndexOutOfBoundsException
{
417 start
= setPartialField(start
, value
, type
);
422 private TimePeriod
setEndField(Integer value
, DateTimeFieldType type
)
423 throws IndexOutOfBoundsException
{
424 end
= setPartialField(end
, value
, type
);
428 // ******************************** internal methods *******************************/
431 * Throws an IndexOutOfBoundsException if the value does not have a valid value
432 * (e.g. month > 12, month < 1, day > 31, etc.)
435 * @throws IndexOutOfBoundsException
437 private static void checkFieldValues(Integer value
, DateTimeFieldType type
, Partial partial
)
438 throws IndexOutOfBoundsException
{
440 if (type
.equals(MONTH_TYPE
)){
443 if (type
.equals(DAY_TYPE
)){
445 Integer month
= null;
446 if (partial
.isSupported(MONTH_TYPE
)){
447 month
= partial
.get(MONTH_TYPE
);
452 }else if (month
== 4 ||month
== 6 ||month
== 9 ||month
== 11){
457 if ( (value
< 1 || value
> max
) ){
458 throw new IndexOutOfBoundsException("Value must be between 1 and " + max
);
462 private void initStart(){
464 start
= new Partial();
468 private void initEnd(){
475 //**************************** to String ****************************************
478 * Returns the {@link #getFreeText()} value if free text is not <code>null</code>.
479 * Otherwise the concatenation of <code>start</code> and <code>end</code> is returned.
481 * @see java.lang.Object#toString()
484 public String
toString(){
485 String result
= null;
486 DateTimeFormatter formatter
= TimePeriodPartialFormatter
.NewInstance();
487 if ( StringUtils
.isNotBlank(this.getFreeText())){
488 result
= this.getFreeText();
490 String strStart
= start
!= null ? start
.toString(formatter
): null;
491 String strEnd
= end
!= null ? end
.toString(formatter
): null;
492 result
= CdmUtils
.concat("-", strStart
, strEnd
);
497 //*********** EQUALS **********************************/
501 * @see java.lang.Object#equals(java.lang.Object)
504 public boolean equals(Object obj
) {
508 if (! (obj
instanceof TimePeriod
)){
511 TimePeriod that
= (TimePeriod
)obj
;
513 if (! CdmUtils
.nullSafeEqual(this.start
, that
.start
)){
516 if (! CdmUtils
.nullSafeEqual(this.end
, that
.end
)){
519 if (! CdmUtils
.nullSafeEqual(this.freeText
, that
.freeText
)){
526 * @see java.lang.Object#hashCode()
529 public int hashCode() {
531 hashCode
= 29*hashCode
+
532 (start
== null?
33: start
.hashCode()) +
533 (end
== null?
39: end
.hashCode()) +
534 (freeText
== null?
41: freeText
.hashCode());
535 return super.hashCode();
539 //*********** CLONE **********************************/
543 * @see java.lang.Object#clone()
546 public Object
clone() {
548 TimePeriod result
= (TimePeriod
)super.clone();
549 result
.setStart(this.start
); //DateTime is immutable
550 result
.setEnd(this.end
);
551 result
.setFreeText(this.freeText
);
553 } catch (CloneNotSupportedException e
) {
554 logger
.warn("Clone not supported exception. Should never occurr !!");