X-Git-Url: https://dev.e-taxonomy.eu/gitweb/cdmlib.git/blobdiff_plain/13964dc6e6b795e95468d8db9a55965bb79abf34..7bbabd80ea28ee49f0e500bc196bd14e63f63006:/cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/common/TimePeriod.java diff --git a/cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/common/TimePeriod.java b/cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/common/TimePeriod.java index e8c277237e..83343854da 100644 --- a/cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/common/TimePeriod.java +++ b/cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/common/TimePeriod.java @@ -9,50 +9,75 @@ package eu.etaxonomy.cdm.model.common; +import java.io.Serializable; import java.util.Calendar; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.persistence.Embeddable; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; import javax.persistence.Transient; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import org.apache.log4j.Logger; import org.hibernate.annotations.Type; +import org.hibernate.search.annotations.Field; +import org.hibernate.search.annotations.FieldBridge; import org.joda.time.DateTime; import org.joda.time.DateTimeFieldType; import org.joda.time.LocalDate; -import org.joda.time.Months; import org.joda.time.Partial; import org.joda.time.ReadableInstant; +import org.joda.time.ReadablePartial; +import org.joda.time.format.DateTimeFormatter; + +import eu.etaxonomy.cdm.common.CdmUtils; +import eu.etaxonomy.cdm.hibernate.PartialBridge; +import eu.etaxonomy.cdm.jaxb.PartialAdapter; /** * @author m.doering * @version 1.0 * @created 08-Nov-2007 13:07:00 + * @updated 05-Dec-2008 23:00:05 */ @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "TimePeriod", propOrder = { "start", - "end" + "end", + "freeText" }) @XmlRootElement(name = "TimePeriod") @Embeddable -public class TimePeriod implements Cloneable { - +public class TimePeriod implements Cloneable, Serializable { private static final Logger logger = Logger.getLogger(TimePeriod.class); + public static final DateTimeFieldType MONTH_TYPE = DateTimeFieldType.monthOfYear(); + public static final DateTimeFieldType YEAR_TYPE = DateTimeFieldType.year(); + public static final DateTimeFieldType DAY_TYPE = DateTimeFieldType.dayOfMonth(); @XmlElement(name = "Start") + @XmlJavaTypeAdapter(value = PartialAdapter.class) + @Type(type="partialUserType") + @Field(index = org.hibernate.search.annotations.Index.UN_TOKENIZED) + @FieldBridge(impl = PartialBridge.class) private Partial start; @XmlElement(name = "End") + @XmlJavaTypeAdapter(value = PartialAdapter.class) + @Type(type="partialUserType") + @Field(index = org.hibernate.search.annotations.Index.UN_TOKENIZED) + @FieldBridge(impl = PartialBridge.class) private Partial end; + @XmlElement(name = "FreeText") + private String freeText; + + /** * Factory method * @return @@ -97,10 +122,10 @@ public class TimePeriod implements Cloneable { Partial startDate = null; Partial endDate = null; if (startYear != null){ - startDate = new Partial().with(DateTimeFieldType.year(), startYear); + startDate = new Partial().with(YEAR_TYPE, startYear); } if (endYear != null){ - endDate = new Partial().with(DateTimeFieldType.year(), endYear); + endDate = new Partial().with(YEAR_TYPE, endYear); } return new TimePeriod(startDate, endDate); } @@ -195,24 +220,73 @@ public class TimePeriod implements Cloneable { end=endDate; } - //@Temporal(TemporalType.TIMESTAMP) - @Type(type="partialUserType") + /** + * True, if this time period represents a period not a single point in time. + * This is by definition, that the time period has a start and an end value, + * and both have a year value that is not null + * @return + */ + @Transient + public boolean isPeriod(){ + if (getStartYear() != null && getEndYear() != null ){ + return true; + }else{ + return false; + } + } + + /** + * True, if there is no start date and no end date and no freetext representation exists. + * @return + */ + @Transient + public boolean isEmpty(){ + if (CdmUtils.isEmpty(this.getFreeText()) && start == null && end == null ){ + return true; + }else{ + return false; + } + } + + public Partial getStart() { return start; } + public void setStart(Partial start) { this.start = start; } - //@Temporal(TemporalType.TIMESTAMP) - @Type(type="partialUserType") public Partial getEnd() { return end; } + public void setEnd(Partial end) { this.end = end; } + /** + * For time periods that need to store more information than the one + * that can be stored in start and end. + * If free text is not null {@link #toString()} will always + * return the free text value. + *
Use {@link #toString()} for public use. + * @return the freeText + */ + public String getFreeText() { + return freeText; + } + + + /** + * Use {@link #parseSingleDate(String)} for public use. + * @param freeText the freeText to set + */ + public void setFreeText(String freeText) { + this.freeText = freeText; + } + + @Transient public String getYear(){ String result = ""; @@ -231,37 +305,35 @@ public class TimePeriod implements Cloneable { @Transient public Integer getStartYear(){ - return getPartialValue(start, DateTimeFieldType.year()); + return getPartialValue(start, YEAR_TYPE); } @Transient public Integer getStartMonth(){ - return getPartialValue(start, DateTimeFieldType.monthOfYear()); + return getPartialValue(start, MONTH_TYPE); } @Transient public Integer getStartDay(){ - return getPartialValue(start, DateTimeFieldType.dayOfMonth()); + return getPartialValue(start, DAY_TYPE); } @Transient public Integer getEndYear(){ - return getPartialValue(end, DateTimeFieldType.year()); + return getPartialValue(end, YEAR_TYPE); } @Transient public Integer getEndMonth(){ - return getPartialValue(end, DateTimeFieldType.monthOfYear()); + return getPartialValue(end, MONTH_TYPE); } @Transient public Integer getEndDay(){ - return getPartialValue(end, DateTimeFieldType.dayOfMonth()); + return getPartialValue(end, DAY_TYPE); } - - @Transient - private Integer getPartialValue(Partial partial, DateTimeFieldType type){ + public static Integer getPartialValue(Partial partial, DateTimeFieldType type){ if (partial == null || ! partial.isSupported(type)){ return null; }else{ @@ -271,67 +343,84 @@ public class TimePeriod implements Cloneable { } public TimePeriod setStartYear(Integer year){ - return setStartField(year, DateTimeFieldType.year()); + return setStartField(year, YEAR_TYPE); } public TimePeriod setStartMonth(Integer month) throws IndexOutOfBoundsException{ - return setStartField(month, DateTimeFieldType.monthOfYear()); + return setStartField(month, MONTH_TYPE); } public TimePeriod setStartDay(Integer day) throws IndexOutOfBoundsException{ - return setStartField(day, DateTimeFieldType.dayOfMonth()); + return setStartField(day, DAY_TYPE); } public TimePeriod setEndYear(Integer year){ - return setEndField(year, DateTimeFieldType.year()); + return setEndField(year, YEAR_TYPE); } public TimePeriod setEndMonth(Integer month) throws IndexOutOfBoundsException{ - return setEndField(month, DateTimeFieldType.monthOfYear()); + return setEndField(month, MONTH_TYPE); } public TimePeriod setEndDay(Integer day) throws IndexOutOfBoundsException{ - return setEndField(day, DateTimeFieldType.dayOfMonth()); + return setEndField(day, DAY_TYPE); } - private TimePeriod setStartField(Integer value, DateTimeFieldType type) throws IndexOutOfBoundsException{ - initStart(); + public static Partial setPartialField(Partial partial, Integer value, DateTimeFieldType type) + throws IndexOutOfBoundsException{ + if (partial == null){ + partial = new Partial(); + } if (value == null){ - start = start.without(type); + return partial.without(type); }else{ - int max = 9999999; - if (type.equals(DateTimeFieldType.monthOfYear())){ - max = 31; - } - if (type.equals(DateTimeFieldType.dayOfMonth())){ - max = 12; - } - if (value < 1 || value > max ){ - throw new IndexOutOfBoundsException("Value must be between 1 and " + max); - } - start = this.start.with(type, value); + checkFieldValues(value, type, partial); + return partial.with(type, value); } + } + + private TimePeriod setStartField(Integer value, DateTimeFieldType type) + throws IndexOutOfBoundsException{ + start = setPartialField(start, value, type); return this; } - private TimePeriod setEndField(Integer value, DateTimeFieldType type){ - initEnd(); - if (value == null){ - end = end.without(type); - }else{ - int max = 9999999; - if (type.equals(DateTimeFieldType.monthOfYear())){ - max = 31; - } - if (type.equals(DateTimeFieldType.dayOfMonth())){ - max = 12; + private TimePeriod setEndField(Integer value, DateTimeFieldType type) + throws IndexOutOfBoundsException{ + end = setPartialField(end, value, type); + return this; + } + + /** + * Throws an IndexOutOfBoundsException if the value does not have a valid value + * (e.g. month > 12, month < 1, day > 31, etc.) + * @param value + * @param type + * @throws IndexOutOfBoundsException + */ + private static void checkFieldValues(Integer value, DateTimeFieldType type, Partial partial) + throws IndexOutOfBoundsException{ + int max = 9999999; + if (type.equals(MONTH_TYPE)){ + max = 12; + } + if (type.equals(DAY_TYPE)){ + max = 31; + Integer month = null; + if (partial.isSupported(MONTH_TYPE)){ + month = partial.get(MONTH_TYPE); } - if ( (value < 1 || value > max) ){ - throw new IndexOutOfBoundsException("Value must be between 1 and " + max); + if (month != null){ + if (month == 2){ + max = 29; + }else if (month == 4 ||month == 6 ||month == 9 ||month == 11){ + max = 30; + } } - end = this.end.with(type, value); } - return this; + if ( (value < 1 || value > max) ){ + throw new IndexOutOfBoundsException("Value must be between 1 and " + max); + } } private void initStart(){ @@ -347,9 +436,219 @@ public class TimePeriod implements Cloneable { } + //patter for first year in string; + private static final Pattern firstYearPattern = Pattern.compile("\\d{4}"); + //case "1806"[1807]; + private static final Pattern uncorrectYearPatter = Pattern.compile("\"\\d{4}\"\\s*\\[\\d{4}\\]"); + //case fl. 1806 or c. 1806 or fl. 1806? + private static final Pattern prefixedYearPattern = Pattern.compile("(fl|c)\\.\\s*\\d{4}(\\s*-\\s*\\d{4})?\\??"); + //standard + private static final Pattern standardPattern = Pattern.compile("\\s*\\d{2,4}(\\s*-(\\s*\\d{2,4})?)?"); + + + public static TimePeriod parseString(TimePeriod timePeriod, String periodString){ + //TODO move to parser class + //TODO until now only quick and dirty (and partly wrong) + TimePeriod result = timePeriod; + + if(timePeriod == null){ + return timePeriod; + } + + if (periodString == null){ + return result; + } + periodString = periodString.trim(); + + result.setFreeText(null); + + //case "1806"[1807]; + if (uncorrectYearPatter.matcher(periodString).matches()){ + result.setFreeText(periodString); + String realYear = periodString.split("\\[")[1]; + realYear = realYear.replace("]", ""); + result.setStartYear(Integer.valueOf(realYear)); + result.setFreeText(periodString); + //case fl. 1806 or c. 1806 or fl. 1806? + }else if(prefixedYearPattern.matcher(periodString).matches()){ + result.setFreeText(periodString); + Matcher yearMatcher = firstYearPattern.matcher(periodString); + yearMatcher.find(); + String startYear = yearMatcher.group(); + result.setStartYear(Integer.valueOf(startYear)); + if (yearMatcher.find()){ + String endYear = yearMatcher.group(); + result.setEndYear(Integer.valueOf(endYear)); + } + }else if (standardPattern.matcher(periodString).matches()){ + String[] years = periodString.split("-"); + Partial dtStart = null; + Partial dtEnd = null; + + if (years.length > 2 || years.length <= 0){ + logger.warn("More than 1 '-' in period String: " + periodString); + }else { + try { + //start + if (! CdmUtils.isEmpty(years[0])){ + dtStart = parseSingleDate(years[0].trim()); + } + + //end + if (years.length >= 2 && ! CdmUtils.isEmpty(years[1])){ + years[1] = years[1].trim(); + if (years[1].length()==2 && dtStart != null && dtStart.isSupported(DateTimeFieldType.year())){ + years[1] = String.valueOf(dtStart.get(DateTimeFieldType.year())/100) + years[1]; + } + dtEnd = parseSingleDate(years[1]); + } + + result.setStart(dtStart); + result.setEnd(dtEnd); + } catch (IllegalArgumentException e) { + //logger.warn(e.getMessage()); + result.setFreeText(periodString); + } + } + }else{ + result.setFreeText(periodString); + } + return result; + } + + public static TimePeriod parseString(String strPeriod) { + TimePeriod timePeriod = TimePeriod.NewInstance(); + return parseString(timePeriod, strPeriod); + } + + + protected static Partial parseSingleDate(String singleDateString) throws IllegalArgumentException{ + //FIXME until now only quick and dirty and incomplete + Partial partial = new Partial(); + singleDateString = singleDateString.trim(); + if (CdmUtils.isNumeric(singleDateString)){ + try { + Integer year = Integer.valueOf(singleDateString.trim()); + if (year < 1000 && year > 2100){ + logger.warn("Not a valid year: " + year + ". Year must be between 1000 and 2100"); + }else if (year < 1700 && year > 2100){ + logger.warn("Not a valid taxonomic year: " + year + ". Year must be between 1750 and 2100"); + partial = partial.with(YEAR_TYPE, year); + }else{ + partial = partial.with(YEAR_TYPE, year); + } + } catch (NumberFormatException e) { + logger.debug("Not a Integer format in getCalendar()"); + throw new IllegalArgumentException(e); + } + }else{ + throw new IllegalArgumentException("Until now only years can be parsed as single dates. But date is: " + singleDateString); + } + return partial; + + } + + + + private class TimePeriodPartialFormatter extends DateTimeFormatter{ + private TimePeriodPartialFormatter(){ + super(null, null); + } + public String print(ReadablePartial partial){ + //TODO + String result = ""; + String year = (partial.isSupported(YEAR_TYPE))? String.valueOf(partial.get(YEAR_TYPE)):null; + String month = (partial.isSupported(MONTH_TYPE))? String.valueOf(partial.get(MONTH_TYPE)):null;; + String day = (partial.isSupported(DAY_TYPE))? String.valueOf(partial.get(DAY_TYPE)):null;; + + if (month !=null){ + if (year == null){ + year = "xxxx"; + } + } + if (day != null){ + if (month == null){ + month = "xx"; + } + if (year == null){ + year = "xxxx"; + } + } + result = (day != null)? day + "." : ""; + result += (month != null)? month + "." : ""; + result += (year != null)? year : ""; + + return result; + } + + } + +//**************************** to String **************************************** + + /** + * Returns the {@link #getFreeText()} value if free text is not null. + * Otherwise the concatenation of start and end is returned. + * + * @see java.lang.Object#toString() + */ + public String toString(){ + String result = null; + DateTimeFormatter formatter = new TimePeriodPartialFormatter(); + if ( CdmUtils.isNotEmpty(this.getFreeText())){ + result = this.getFreeText(); + }else{ + String strStart = start != null ? start.toString(formatter): null; + String strEnd = end != null ? end.toString(formatter): null; + result = CdmUtils.concat("-", strStart, strEnd); + } + return result; + } + +//*********** EQUALS **********************************/ + + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (obj == null){ + return false; + } + if (! (obj instanceof TimePeriod)){ + return false; + } + TimePeriod that = (TimePeriod)obj; + + if (! CdmUtils.nullSafeEqual(this.start, that.start)){ + return false; + } + if (! CdmUtils.nullSafeEqual(this.end, that.end)){ + return false; + } + if (! CdmUtils.nullSafeEqual(this.freeText, that.freeText)){ + return false; + } + return true; + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + int hashCode = 7; + hashCode = 29*hashCode + + (start== null? 33: start.hashCode()) + + (end== null? 39: end.hashCode()) + + (freeText== null? 41: freeText.hashCode()); + return super.hashCode(); + } + //*********** CLONE **********************************/ + /* (non-Javadoc) * @see java.lang.Object#clone() */ @@ -358,12 +657,14 @@ public class TimePeriod implements Cloneable { try { TimePeriod result = (TimePeriod)super.clone(); result.setStart(this.start); //DateTime is immutable - result.setEnd(this.end); + result.setEnd(this.end); + result.setFreeText(this.freeText); return result; } catch (CloneNotSupportedException e) { logger.warn("Clone not supported exception. Should never occurr !!"); return null; } } + } \ No newline at end of file