/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jbpm.calendar;
import java.io.Serializable;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import org.jbpm.JbpmException;
/**
* interprets textual descriptions of a duration.
* <p>
* Syntax: <quantity> [business] <unit> <br />
* Where
* <ul>
* <li><quantity> is a piece of text that is parsable with
* <code>NumberFormat.getNumberInstance().parse(quantity)</code>. </li>
* <li><unit> is one of {second, seconds, minute, minutes, hour, hours, day, days, week,
* weeks, month, months, year, years}. </li>
* <li>And adding the optional indication <code>business</code> means that only business hours
* should be taken into account for this duration. </li>
* </ul>
* </p>
*/
public class Duration implements Serializable {
private static final long serialVersionUID = 2L;
static final long SECOND = 1000;
static final long MINUTE = 60 * SECOND;
static final long HOUR = 60 * MINUTE;
static final long DAY = 24 * HOUR;
static final long WEEK = 7 * DAY;
static final long BUSINESS_DAY;
static final long BUSINESS_WEEK;
static final long BUSINESS_MONTH;
static final long BUSINESS_YEAR;
static {
Properties businessCalendarProperties = BusinessCalendar.getBusinessCalendarProperties();
String businessDayText = businessCalendarProperties.getProperty("business.day.expressed.in.hours");
String businessWeekText = businessCalendarProperties.getProperty("business.week.expressed.in.hours");
String businessMonthText = businessCalendarProperties.getProperty("business.month.expressed.in.business.days");
String businessYearText = businessCalendarProperties.getProperty("business.year.expressed.in.business.days");
try {
NumberFormat format = NumberFormat.getNumberInstance(Locale.US);
BUSINESS_DAY = multiply(format.parse(businessDayText), HOUR);
BUSINESS_WEEK = multiply(format.parse(businessWeekText), HOUR);
BUSINESS_MONTH = multiply(format.parse(businessMonthText), BUSINESS_DAY);
BUSINESS_YEAR = multiply(format.parse(businessYearText), BUSINESS_DAY);
}
catch (ParseException e) {
throw new NumberFormatException(e.getMessage());
}
}
private static long multiply(Number n1, long n2) {
return n1 instanceof Long ? n1.longValue() * n2 : (long) (n1.doubleValue() * n2);
}
static Map calendarFields = new HashMap();
static {
Integer millisecondField = new Integer(Calendar.MILLISECOND);
calendarFields.put("millisecond", millisecondField);
calendarFields.put("milliseconds", millisecondField);
Integer secondField = new Integer(Calendar.SECOND);
calendarFields.put("second", secondField);
calendarFields.put("seconds", secondField);
Integer minuteField = new Integer(Calendar.MINUTE);
calendarFields.put("minute", minuteField);
calendarFields.put("minutes", minuteField);
Integer hourField = new Integer(Calendar.HOUR);
calendarFields.put("hour", hourField);
calendarFields.put("hours", hourField);
Integer dayField = new Integer(Calendar.DAY_OF_MONTH);
calendarFields.put("day", dayField);
calendarFields.put("days", dayField);
Integer weekField = new Integer(Calendar.WEEK_OF_MONTH);
calendarFields.put("week", weekField);
calendarFields.put("weeks", weekField);
Integer monthField = new Integer(Calendar.MONTH);
calendarFields.put("month", monthField);
calendarFields.put("months", monthField);
Integer yearField = new Integer(Calendar.YEAR);
calendarFields.put("year", yearField);
calendarFields.put("years", yearField);
}
static Map businessAmounts = new HashMap();
static {
Long secondAmount = new Long(SECOND);
businessAmounts.put("business second", secondAmount);
businessAmounts.put("business seconds", secondAmount);
Long minuteAmount = new Long(MINUTE);
businessAmounts.put("business minute", minuteAmount);
businessAmounts.put("business minutes", minuteAmount);
Long hourAmount = new Long(HOUR);
businessAmounts.put("business hour", hourAmount);
businessAmounts.put("business hours", hourAmount);
Long dayAmount = new Long(BUSINESS_DAY);
businessAmounts.put("business day", dayAmount);
businessAmounts.put("business days", dayAmount);
Long weekAmount = new Long(BUSINESS_WEEK);
businessAmounts.put("business week", weekAmount);
businessAmounts.put("business weeks", weekAmount);
Long monthAmount = new Long(BUSINESS_MONTH);
businessAmounts.put("business month", monthAmount);
businessAmounts.put("business months", monthAmount);
Long yearAmount = new Long(BUSINESS_YEAR);
businessAmounts.put("business year", yearAmount);
businessAmounts.put("business years", yearAmount);
}
private int field;
private long amount;
private boolean isBusinessTime;
Duration() {
}
public Duration(long milliseconds) {
amount = milliseconds;
field = Calendar.MILLISECOND;
}
public Duration(Duration duration) {
field = duration.field;
amount = duration.amount;
isBusinessTime = duration.isBusinessTime;
}
/**
* creates a duration from a textual description. syntax: {number} space {unit} where number is
* parsable to a java.lang.Number and unit is one of
* <ul>
* <li>second</li>
* <li>seconds</li>
* <li>minute</li>
* <li>minutes</li>
* <li>hour</li>
* <li>hours</li>
* <li>day</li>
* <li>days</li>
* <li>week</li>
* <li>weeks</li>
* <li>month</li>
* <li>months</li>
* <li>year</li>
* <li>years</li>
* </ul>
*/
public Duration(String duration) {
if (duration == null)
throw new JbpmException("duration is null");
int index = indexOfNonWhite(duration, 0);
char lead = duration.charAt(index);
if (lead == '+' || lead == '-')
++index;
// parse quantity
NumberFormat format = NumberFormat.getNumberInstance(Locale.US);
index = indexOfNonWhite(duration, index);
ParsePosition position = new ParsePosition(index);
Number quantity = format.parse(duration, position);
if (quantity == null)
throw new IllegalArgumentException("improper format of duration '" + duration + "'");
String unitText = duration.substring(position.getIndex()).trim();
if (unitText.startsWith("business")) {
// parse unit
Long unit = (Long) businessAmounts.get(unitText);
if (unit == null)
throw new IllegalArgumentException("improper format of duration '" + duration + "'");
field = Calendar.MILLISECOND;
amount = multiply(quantity, unit.longValue());
isBusinessTime = true;
}
else {
// parse unit
Integer unit = (Integer) calendarFields.get(unitText);
if (unit == null)
throw new IllegalArgumentException("improper format of duration '" + duration + "'");
// is quantity exactly representable as int?
if (quantity instanceof Long && isInteger(quantity.longValue())) {
field = unit.intValue();
amount = quantity.longValue();
}
else {
field = Calendar.MILLISECOND;
switch (unit.intValue()) {
case Calendar.SECOND:
amount = (long) (quantity.doubleValue() * SECOND);
break;
case Calendar.MINUTE:
amount = (long) (quantity.doubleValue() * MINUTE);
break;
case Calendar.HOUR:
amount = (long) (quantity.doubleValue() * HOUR);
break;
case Calendar.DAY_OF_MONTH:
amount = (long) (quantity.doubleValue() * DAY);
break;
case Calendar.WEEK_OF_MONTH:
amount = (long) (quantity.doubleValue() * WEEK);
break;
default:
throw new IllegalArgumentException("fractional amount not supported for unit '"
+ unitText
+ "'");
}
}
}
if (lead == '-')
amount = -amount;
}
private static int indexOfNonWhite(String str, int fromIndex) {
int off = fromIndex;
int len = str.length();
while (off < len && str.charAt(off) <= ' ')
off++;
return off;
}
private static boolean isInteger(long number) {
return number >= Integer.MIN_VALUE && number <= Integer.MAX_VALUE;
}
public Date addTo(Date date) {
if (field == Calendar.MILLISECOND)
return new Date(date.getTime() + amount);
Calendar calendar = BusinessCalendar.getCalendar();
calendar.setTime(date);
calendar.add(field, (int) amount);
return calendar.getTime();
}
public long getMilliseconds() {
switch (field) {
case Calendar.MILLISECOND:
return amount;
case Calendar.SECOND:
return amount * SECOND;
case Calendar.MINUTE:
return amount * MINUTE;
case Calendar.HOUR:
return amount * HOUR;
case Calendar.DAY_OF_MONTH:
return amount * DAY;
case Calendar.WEEK_OF_MONTH:
return amount * WEEK;
default:
throw new UnsupportedOperationException("calendar field '"
+ field
+ "' does not have a fixed duration");
}
}
public boolean isBusinessTime() {
return isBusinessTime;
}
}