/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is part of dcm4che, an implementation of DICOM(TM) in * Java(TM), hosted at https://github.com/gunterze/dcm4che. * * The Initial Developer of the Original Code is * Agfa Healthcare. * Portions created by the Initial Developer are Copyright (C) 2011 * the Initial Developer. All Rights Reserved. * * Contributor(s): * See @authors listed below * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ package org.dcm4chee.archive.query.util; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import org.dcm4che3.data.Attributes; import org.dcm4che3.data.DateRange; import org.dcm4che3.util.DateUtils; import com.mysema.query.BooleanBuilder; import com.mysema.query.types.ExpressionUtils; import com.mysema.query.types.Predicate; import com.mysema.query.types.expr.BooleanExpression; import com.mysema.query.types.path.DateTimePath; import com.mysema.query.types.path.StringPath; /** * @author Gunter Zeilinger <gunterze@gmail.com> * @author Michael Backhaus <michael.backhaus@agfa.com> * @author Hesham Elbadawi <bsdreko@gmail.com> */ public class MatchDateTimeRange { static enum ComparisonOperator { GT, GE, EQ, LT, LTE; } public static enum FormatDate { DA { @Override String format(Date date) { return DateUtils.formatDA(null, date); } }, TM { @Override String format(Date date) { return DateUtils.formatTM(null, date); } }, DT { @Override String format(Date date) { return DateUtils.formatDT(null, date); } }; abstract String format(Date date); } static Predicate rangeMatch(DateTimePath<Date> path, Attributes keys, int tag, FormatDate dt, boolean matchUnknown) { DateRange dateRange = keys.getDateRange(tag, null); if (dateRange == null) return null; return matchUnknown ? ExpressionUtils.or(range(path, dateRange, dt, keys.getString(tag)), path.isNull()) : ExpressionUtils.and(range(path, dateRange, dt, keys.getString(tag)), path.isNotNull()); } static Predicate rangeMatch(StringPath path, Attributes keys, int tag, FormatDate dt, boolean matchUnknown) { DateRange dateRange = keys.getDateRange(tag, null); if (dateRange == null) return null; return matchUnknown(path, matchUnknown, range(path, dateRange, dt)); } static Predicate rangeMatch(DateTimePath<Date> dateTimeField, int dateTag, int timeTag, long dateAndTimeTag, Attributes keys, boolean combinedDatetimeMatching, boolean matchUnknown) { final boolean containsDateTag = keys.containsValue(dateTag); final boolean containsTimeTag = keys.containsValue(timeTag); if (!containsDateTag && !containsTimeTag) return null; BooleanBuilder predicates = new BooleanBuilder(); if (containsDateTag && containsTimeTag && combinedDatetimeMatching) { predicates.and(matchUnknown ? ExpressionUtils.or(combinedRange(dateTimeField, keys.getDateRange(dateAndTimeTag, null)), dateTimeField.isNull()) : ExpressionUtils.and(combinedRange(dateTimeField, keys.getDateRange(dateAndTimeTag, null)), dateTimeField.isNotNull())); } else { if (containsDateTag) predicates.and(matchUnknown ? ExpressionUtils.or(range(dateTimeField, keys.getDateRange(dateTag, null), FormatDate.DA, keys.getString(timeTag)), dateTimeField.isNull()) : ExpressionUtils.and(range(dateTimeField, keys.getDateRange(dateTag, null), FormatDate.DA, keys.getString(timeTag)), dateTimeField.isNotNull())); if (containsTimeTag) predicates.and(matchUnknown ? ExpressionUtils.or(range(dateTimeField, adjustDateComponents(keys.getDateRange(dateTag),keys.getDateRange(timeTag, null)), FormatDate.TM, keys.getString(timeTag)), dateTimeField.isNull()) : ExpressionUtils.and(range(dateTimeField, adjustDateComponents(keys.getDateRange(dateTag),keys.getDateRange(timeTag, null)), FormatDate.TM, keys.getString(timeTag)), dateTimeField.isNotNull())); } return predicates; } private static DateRange adjustDateComponents(DateRange dtRange, DateRange tmRange) { return tmRange; } @Deprecated static Predicate rangeMatch(StringPath dateField, StringPath timeField, int dateTag, int timeTag, long dateAndTimeTag, Attributes keys, boolean combinedDatetimeMatching, boolean matchUnknown) { final boolean containsDateTag = keys.containsValue(dateTag); final boolean containsTimeTag = keys.containsValue(timeTag); if (!containsDateTag && !containsTimeTag) return null; BooleanBuilder predicates = new BooleanBuilder(); if (containsDateTag && containsTimeTag && combinedDatetimeMatching) { predicates.and(matchUnknown(dateField, matchUnknown, combinedRange(dateField, timeField, keys.getDateRange(dateAndTimeTag, null)))); } else { if (containsDateTag) predicates.and(matchUnknown(dateField, matchUnknown, range(dateField, keys.getDateRange(dateTag, null), FormatDate.DA))); if (containsTimeTag) predicates.and(matchUnknown(timeField, matchUnknown, range(timeField, keys.getDateRange(timeTag, null), FormatDate.TM))); } return predicates; } private static Predicate matchUnknown(StringPath field, boolean matchUnknown, Predicate predicate) { return matchUnknown ? ExpressionUtils.or(predicate, field.eq("*")) : ExpressionUtils.and(predicate, field.ne("*")); } private static Predicate range(StringPath field, DateRange range, FormatDate dt) { Date startDate = range.getStartDate(); Date endDate = range.getEndDate(); if (startDate == null) return field.loe(dt.format(endDate)); if (endDate == null) return field.goe(dt.format(startDate)); return rangeInterval(field, startDate, endDate, dt, range); } private static Predicate range(DateTimePath<java.util.Date> dateTimeField, DateRange range, FormatDate dt, String timeKey) { Date startDateTime = range.getStartDate(); Date endDateTime = range.getEndDate(); if (startDateTime == null && dt == FormatDate.DA || dt == FormatDate.DT) return dateTimeField.loe(endDateTime); if (startDateTime == null && dt == FormatDate.TM) { Calendar timeCalendar = new GregorianCalendar(); timeCalendar.setTime(endDateTime); return timeLessThan(dateTimeField, timeCalendar).or(timeEqual(dateTimeField, timeCalendar, timeKey)); } if (endDateTime == null && dt == FormatDate.DA || dt == FormatDate.DT) return dateTimeField.goe(startDateTime); if (endDateTime == null && dt == FormatDate.TM) { Calendar timeCalendar = new GregorianCalendar(); timeCalendar.setTime(startDateTime); return timeGreaterThan(dateTimeField, timeCalendar).or(timeEqual(dateTimeField, timeCalendar, timeKey)); } return rangeInterval(dateTimeField, startDateTime, endDateTime, dt, range, timeKey); } private static Predicate rangeInterval(DateTimePath<java.util.Date> field, Date startDate, Date endDate, FormatDate dt, DateRange range, String timeKey) { Calendar startCal = new GregorianCalendar(); Calendar endCal = new GregorianCalendar(); startCal.setTime(startDate); endCal.setTime(endDate); if(dt.equals(FormatDate.TM) && range.isStartDateExeedsEndDate()){ Calendar midnightLow = new GregorianCalendar(); midnightLow.setTime(startDate); midnightLow.set(Calendar.HOUR_OF_DAY, 23); midnightLow.set(Calendar.MINUTE, 59); midnightLow.set(Calendar.SECOND,59); midnightLow.set(Calendar.MILLISECOND,999); Calendar midnightHigh = new GregorianCalendar(); midnightHigh.setTime(endDate); midnightHigh.set(Calendar.HOUR_OF_DAY, 0); midnightHigh.set(Calendar.MINUTE, 0); midnightHigh.set(Calendar.SECOND,0); midnightHigh.set(Calendar.MILLISECOND,0); return ExpressionUtils.or((timeGreaterThan(field, startCal).or(timeEqual(field, startCal, timeKey))).and(timeLessThan(field, midnightLow).or(timeEqual(field, midnightLow, timeKey))), (timeGreaterThan(field, midnightHigh).or(timeEqual(field, midnightHigh, timeKey))).and(timeLessThan(field, endCal)).or(timeEqual(field, endCal, timeKey))); } else { return dt == FormatDate.DA || dt == FormatDate.DT ? dateEqual(startCal, endCal) ? dateEqual(field, startCal) : field.between(startDate, endDate) : timeEquals(startCal, endCal) ? timeEqual(field, startCal, timeKey) : (timeGreaterThan(field, startCal).or(timeEqual(field, startCal, timeKey))).and(timeLessThan(field, endCal).or(timeEqual(field, endCal, timeKey))); } } private static boolean timeEquals(Calendar time1, Calendar time2) { return time1.get(Calendar.HOUR_OF_DAY) == time2.get(Calendar.HOUR_OF_DAY) && time1.get(Calendar.MINUTE) == time2.get(Calendar.MINUTE) && time1.get(Calendar.SECOND) == time2.get(Calendar.SECOND); } private static Predicate rangeInterval(StringPath field, Date startDate, Date endDate, FormatDate dt, DateRange range) { String start = dt.format(startDate); String end = dt.format(endDate); if (dt.equals(FormatDate.TM) && range.isStartDateExeedsEndDate()) { String midnightLow = "115959.999"; String midnightHigh = "000000.000"; return ExpressionUtils.or(field.between(start, midnightLow), field.between(midnightHigh, end)); } else { return end.equals(start) ? field.eq(start) : field.between(start, end); } } private static Predicate combinedRange(DateTimePath<java.util.Date> dateTimeField, DateRange dateRange) { if (dateRange.getStartDate() == null) return combinedRangeEnd(dateTimeField, dateRange.getEndDate()); if (dateRange.getEndDate() == null) return combinedRangeStart(dateTimeField, dateRange.getStartDate()); return combinedRangeInterval(dateTimeField, dateRange.getStartDate(), dateRange.getEndDate()); } private static Predicate combinedRange(StringPath dateField, StringPath timeField, DateRange dateRange) { if (dateRange.getStartDate() == null) return combinedRangeEnd(dateField, timeField, DateUtils.formatDA(null, dateRange.getEndDate()), DateUtils.formatTM(null, dateRange.getEndDate())); if (dateRange.getEndDate() == null) return combinedRangeStart(dateField, timeField, DateUtils.formatDA(null, dateRange.getStartDate()), DateUtils.formatTM(null, dateRange.getStartDate())); return combinedRangeInterval(dateField, timeField, dateRange.getStartDate(), dateRange.getEndDate()); } private static Predicate combinedRangeInterval(DateTimePath<java.util.Date> dateTimeField, Date startDateTimeRange, Date endDateTimeRange) { return dateTimeField.between(startDateTimeRange, endDateTimeRange); } private static Predicate combinedRangeInterval(StringPath dateField, StringPath timeField, Date startDateRange, Date endDateRange) { String startTime = DateUtils.formatTM(null, startDateRange); String endTime = DateUtils.formatTM(null, endDateRange); String startDate = DateUtils.formatDA(null, startDateRange); String endDate = DateUtils.formatDA(null, endDateRange); return endDate.equals(startDate) ? ExpressionUtils.allOf(dateField.eq(startDate), timeField.goe(startTime), timeField.loe(endTime)) : ExpressionUtils.and( combinedRangeStart(dateField, timeField, startDate, startTime), combinedRangeEnd(dateField, timeField, endDate, endTime)); } private static Predicate combinedRangeEnd(DateTimePath<java.util.Date> dateTimeField , Date endDateAndTime) { return dateTimeField.loe(endDateAndTime); } private static Predicate combinedRangeEnd(StringPath dateField, StringPath timeField, String endDate, String endTime) { Predicate endDayTime = ExpressionUtils.and(dateField.eq(endDate), timeField.loe(endTime)); Predicate endDayTimeUnknown = ExpressionUtils.and(dateField.eq(endDate), timeField.eq("*")); Predicate endDayPrevious = dateField.lt(endDate); return ExpressionUtils.anyOf(endDayTime, endDayTimeUnknown, endDayPrevious); } private static Predicate combinedRangeStart(StringPath dateField, StringPath timeField, String startDate, String startTime) { Predicate startDayTime = ExpressionUtils.and(dateField.eq(startDate), timeField.goe(startTime)); Predicate startDayTimeUnknown = ExpressionUtils.and(dateField.eq(startDate), timeField.eq("*")); Predicate startDayFollowing = dateField.gt(startDate); return ExpressionUtils.anyOf(startDayTime, startDayTimeUnknown, startDayFollowing); } private static Predicate combinedRangeStart(DateTimePath<java.util.Date> dateTimeField , Date startDateTime) { return dateTimeField.goe(startDateTime); } private static BooleanExpression timeGreaterThan(DateTimePath<java.util.Date> dateTimeField, Calendar startDateTimeCal) { return dateTimeField.hour().gt(startDateTimeCal.get(Calendar.HOUR_OF_DAY)) .or(dateTimeField.hour().eq(startDateTimeCal.get(Calendar.HOUR_OF_DAY)) .and(dateTimeField.minute().gt(startDateTimeCal.get(Calendar.MINUTE)))) .or(dateTimeField.hour().eq(startDateTimeCal.get(Calendar.HOUR_OF_DAY)) .and(dateTimeField.minute().eq(startDateTimeCal.get(Calendar.MINUTE)) .and(dateTimeField.second().gt(startDateTimeCal.get(Calendar.SECOND))))); } private static BooleanExpression timeLessThan(DateTimePath<java.util.Date> dateTimeField, Calendar endDateTimeCal) { return dateTimeField.hour().lt(endDateTimeCal.get(Calendar.HOUR_OF_DAY)) .or(dateTimeField.hour().eq(endDateTimeCal.get(Calendar.HOUR_OF_DAY)) .and(dateTimeField.minute().lt(endDateTimeCal.get(Calendar.MINUTE)))) .or(dateTimeField.hour().eq(endDateTimeCal.get(Calendar.HOUR_OF_DAY)) .and(dateTimeField.minute().eq(endDateTimeCal.get(Calendar.MINUTE)) .and(dateTimeField.second().lt(endDateTimeCal.get(Calendar.SECOND))))); } private static BooleanExpression dateEqual(DateTimePath<java.util.Date> dateTimeField, Calendar dateCal) { return dateTimeField.year().eq(dateCal.get(Calendar.YEAR)) .and(dateTimeField.month().eq(dateCal.get(Calendar.MONTH) + 1) //compensate for POSIX representation of month .and(dateTimeField.dayOfMonth().eq(dateCal.get(Calendar.DAY_OF_MONTH)))); } private static BooleanExpression timeEqual(DateTimePath<java.util.Date> dateTimeField, Calendar timeCal, String timeKey) { return addBasicTimeComponentsEqualsExpression(dateTimeField, timeKey, timeCal); } private static BooleanExpression addBasicTimeComponentsEqualsExpression(DateTimePath<java.util.Date> dateTimeField, String time, Calendar timeCal) { BooleanExpression exp ; switch (time.length()) { case 2: exp = dateTimeField.hour().eq(timeCal.get(Calendar.HOUR_OF_DAY)); break; case 4: exp = dateTimeField.hour().eq(timeCal.get(Calendar.HOUR_OF_DAY)) .and(dateTimeField.minute().eq(timeCal.get(Calendar.MINUTE))); break; case 6: exp = dateTimeField.hour().eq(timeCal.get(Calendar.HOUR_OF_DAY)) .and(dateTimeField.minute().eq(timeCal.get(Calendar.MINUTE))) .and(dateTimeField.second().eq(timeCal.get(Calendar.SECOND))); break; default: exp = dateTimeField.hour().eq(timeCal.get(Calendar.HOUR_OF_DAY)) .and(dateTimeField.minute().eq(timeCal.get(Calendar.MINUTE))) .and(dateTimeField.second().eq(timeCal.get(Calendar.SECOND))); break; } return exp; } private static boolean dateEqual(Calendar date1, Calendar date2) { return date1.get(Calendar.YEAR) == date2.get(Calendar.YEAR) && date1.get(Calendar.MONTH) == date2.get(Calendar.MONTH) && date1.get(Calendar.DAY_OF_MONTH) == date2.get(Calendar.DAY_OF_MONTH); } }