/* See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * Esri Inc. licenses this file to You under the Apache License, Version 2.0 * (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.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.esri.gpt.framework.util; import com.esri.gpt.framework.jsf.MessageBroker; import com.esri.gpt.framework.util.TimePeriod.DayOfWeek; import com.esri.gpt.framework.util.TimePeriod.Unit; import java.io.Serializable; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Time period. * Represents time period in one of various forms indicating a number of * milliseconds to elapse until defined moment in time.</br> * Generic way of creating instance of the class is to use static method * {@link TimePeriod#parseValue(java.lang.String)}. That method understands several * different ways of expressing a time period. * </p> * The simplest way of creating time period is by providing number of milliseconds. * Use a simple integer number like this: </br> * <pre> * TimePeriod.parse("1000");</pre> * to create a a constant, one second period. * </p> * Another way to create period is to use one of the values from {@link TimePeriod.Unit}. * Combining integer value with the unit name simplifies expressing longer periods, for example:</br> * <pre> * TimePeriod.parse("2[DAY]");</pre> * gives just two days constant period. * </p> * There is also a way to express relative periods. Giving: * <pre> * TimePeriod.parse("08:00");</pre> * gives a period from now on to the closest eight o'clock am every day. * </p> * It is also possible to use one of the values of {@link TimePeriod.DayOfWeek}, like this: * <pre> * TimePeriod.parse("monday");</pre> * which means period from now on to the begining of the closes Monday, or even more specific way: * <pre> * TimePeriod.parse("monday 08:00");</pre> * to have period from now on to the eight o'clock am of the Monday. * </p> * It is also possible to parse array of time periods:</br> * <pre> * TimePeriod.parse("08:00,sunday 10:00");</pre> * which gives period of eight o'clock every day and ten o'clock every sunday, whatever * comes first. */ public final class TimePeriod implements Serializable { // class variables ============================================================= // instance variables ========================================================== /** wait time provider */ private WaitTimeProvider waitTimeProvider; // constructors ================================================================ /** * Creates instance of time period. */ public TimePeriod() { } /** * Creates instance of time period. * @param value value in milliseconds */ public TimePeriod(long value) { setValue(value); } /** * Creates instance of time period. * @param value value in milliseconds * @param unit unit */ public TimePeriod(long value, Unit unit) { setValue(value, unit); } /** * Creates instance of time period. * @param waitTimeProvider wait time provider */ private TimePeriod(WaitTimeProvider waitTimeProvider) { this.waitTimeProvider = waitTimeProvider; } // properties ================================================================== /** * Gets value. * @return value in milliseconds */ public long getValue() { return waitTimeProvider != null ? waitTimeProvider.getWaitTime(new Date()) : 0; } /** * Gets value. * @param since since * @return value in milliseconds */ public long getValue(Date since) { return waitTimeProvider != null ? waitTimeProvider.getWaitTime(since) : 0; } /** * Sets value. * @param value value in milliseconds */ public void setValue(long value) { waitTimeProvider = new LongWaitTimeProvider(value); } /** * Sets value. * @param value value * @param unit unit */ public void setValue(long value, Unit unit) { waitTimeProvider = new UnitWaitTimeProvider(value, unit); } // methods ===================================================================== /** * Parses value into time period. * @param value value to parse * @return time period * @throws IllegalArgumentException if unable to parse value */ public static TimePeriod parseValue(String value) throws IllegalArgumentException { WaitTimeProvider wtp = SnowBallWaitTimeProvider.parse(value); if (wtp!=null) return new TimePeriod(wtp); wtp = WaitTimeProviderFactory.create(value); if (wtp!=null) return new TimePeriod(wtp); throw new IllegalArgumentException("Illegal period value: " + value); } /** * Creates string representation of the object. * @return string representation of the object */ @Override public String toString() { long value = getValue(); StringBuilder sb = new StringBuilder(); Unit[] vals = Unit.values(); for (int i = vals.length - 1; i >= 0; i--) { Unit u = vals[i]; if (!u.isToStringeable()) continue; if (value >= u.getMilliseconds()) { long part = value / u.getMilliseconds(); value = value % u.getMilliseconds(); if (sb.length() > 0) sb.append(", "); sb.append(Long.toString(part)).append(" ").append(u.name().toLowerCase()).append(part > 1 ? "s" : ""); } } return sb.length() > 0 ? sb.toString() : "0"; } /** * Converts into localized string. * @param mb message broker * @return localized string */ public String toLocalizedString(MessageBroker mb) { long value = getValue(); StringBuilder sb = new StringBuilder(); Unit[] vals = Unit.values(); for (int i = vals.length - 1; i >= 0; i--) { Unit u = vals[i]; if (!u.isToLocalizedStringeable()) continue; if (value >= u.getMilliseconds()) { long part = value / u.getMilliseconds(); value = value % u.getMilliseconds(); if (part>0) { String resKey = u.getResourceKey(); if (part>1) { resKey += "s"; // for plural } String message = mb.retrieveMessage(resKey, new String[]{Long.toString(part)}); if (message.length()>0) { if (sb.length() > 0) sb.append(", "); sb.append(message); } } } } return sb.toString(); } // enums ======================================================================= /** * Time unit. */ public enum Unit { /** millisecond */ millisecond(1L, true, false), /** second */ second(1000L), /** minute */ minute(60000L), /** hour */ hour(3600000L), /** day */ day(86400000L), /** week */ week(604800000L, false, false), /** month */ month(2592000000L); /** number of milliseconds in one unit */ private long _milliseconds; /** ability to use in toString */ private boolean _toStringeable = true; /** ability to use in toLocalizdString */ private boolean _toLocalizedStringeable = true; /** * Creates instance of the unit. * @param milliseconds number of milliseconds in one unit */ Unit(long milliseconds) { _milliseconds = milliseconds; } /** * Creates instance of the unit. * @param milliseconds number of milliseconds in one unit * @param toStringeable <code>true</code> if can be used in toString */ Unit(long milliseconds, boolean toStringeable, boolean toLocalizedStringeable) { _milliseconds = milliseconds; _toStringeable = toStringeable; _toLocalizedStringeable = toLocalizedStringeable; } /** * Checks string value. * @param value string representation of the value * @return value represented by string or {@link Unit#millisecond} if value * doesn't represent valid unit */ public static Unit checkValueOf(String value) { value = Val.chkStr(value); for (Unit u : values()) { if (u.name().equalsIgnoreCase(value)) { return u; } } return millisecond; } /** * Gets milliseconds. * @return number of milliseconds in one unit */ public long getMilliseconds() { return _milliseconds; } public boolean isToStringeable() { return _toStringeable; } public boolean isToLocalizedStringeable() { return _toLocalizedStringeable; } private String getResourceKey() { return "catalog.TimePeriod.Unit" + "." + name(); } } /** * Day of the week. */ public enum DayOfWeek { Sunday(new String[]{"Sun", "Su"}), Monday(new String[]{"Mon", "Mo"}), Tuesday(new String[]{"Tue", "Tu"}), Wednesday(new String[]{"Wed", "We"}), Thursday(new String[]{"Thu", "Th"}), Friday(new String[]{"Fri", "Fr"}), Saturday(new String[]{"Sat", "Sa"}),; private String[] altNames; DayOfWeek(String[] altNames) { this.altNames = altNames; } public static DayOfWeek checkValueOf(String value) { value = Val.chkStr(value); for (DayOfWeek u : values()) { if (u.is(value)) { return u; } } return null; } private boolean is(String name) { if (name().equalsIgnoreCase(name)) { return true; } for (String altName : altNames) { if (altName.equalsIgnoreCase(name)) { return true; } } return false; } } } /** * Wait time provider factory. */ class WaitTimeProviderFactory { /** * Creates wait time provider based on the value. * @param value value * @return instance of the wait time provider or <code>null</code> if value can not be parsed */ public static WaitTimeProvider create(String value) { WaitTimeProvider wtp; wtp = LongWaitTimeProvider.parse(value); if (wtp != null) return wtp; wtp = UnitWaitTimeProvider.parse(value); if (wtp != null) return wtp; wtp = DayOfWeekTimeProvider.parse(value); if (wtp != null) return wtp; wtp = HourOfDayTimeProvider.parse(value); if (wtp != null) return wtp; return null; } } /** * Wait time provider. */ interface WaitTimeProvider extends Serializable { /** * Gets wait time. * @param since since date * @return wait time in milliseconds */ long getWaitTime(Date since); } /** * Wait time provider by long value. */ class LongWaitTimeProvider implements WaitTimeProvider { /** wait time */ private long waitTime; /** * Creates instance of the provider. * @param waitTime wait time given as value in milliseconds */ public LongWaitTimeProvider(long waitTime) { this.waitTime = waitTime; } @Override public long getWaitTime(Date since) { return waitTime; } /** * Parses value into wait time provider. * @param value value * @return provider or <code>null</code> if can not be parsed */ public static WaitTimeProvider parse(String value) { value = Val.chkStr(value); try { return new LongWaitTimeProvider(Long.parseLong(value)); } catch (NumberFormatException ex) { return null; } } } /** * Wait time provider for time unit. */ class UnitWaitTimeProvider implements WaitTimeProvider { /** unit */ private Unit unit; /** value */ private long value; /** * Creates instance of the provider. * @param value value * @param unit unit */ public UnitWaitTimeProvider(long value, Unit unit) { this.unit = unit; this.value = value; } @Override public long getWaitTime(Date since) { return value * unit.getMilliseconds(); } /** * Parses value into wait time provider. * @param value value * @return provider or <code>null</code> if can not be parsed */ public static WaitTimeProvider parse(String value) { value = Val.chkStr(value).toLowerCase(); Unit unit = null; for (Unit u : Unit.values()) { String patternDef = "\\[\\p{Blank}*" + u.name().toLowerCase() + "\\p{Blank}*\\]"; Pattern pattern = Pattern.compile(patternDef); Matcher matcher = pattern.matcher(value); if (matcher.find()) { int index = matcher.start(); if (index >= 0) { unit = u; value = value.substring(0, index).trim(); break; } } } if (unit != null) { try { return new UnitWaitTimeProvider( Long.parseLong(value), unit); } catch (NumberFormatException ex) { return null; } } else { return null; } } } /** * Hour of the day time provider. */ class HourOfDayTimeProvider implements WaitTimeProvider { /** time of the day */ private Calendar at; /** * Creates instance of the provider. * @param time time */ public HourOfDayTimeProvider(Date time) { this.at = createAtDate(time); } @Override public long getWaitTime(Date since) { Calendar cal = Calendar.getInstance(); cal.setTime(since); cal.set(0, 0, 0, cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), 0); if (cal.before(at)) { return at.getTimeInMillis() - cal.getTimeInMillis(); } else { return Unit.day.getMilliseconds() - (at.getTimeInMillis() - cal.getTimeInMillis()); } } /** * Parses value into wait time provider. * @param value value * @return provider or <code>null</code> if can not be parsed */ public static WaitTimeProvider parse(String value) { value = Val.chkStr(value); String[] hourMin = value.split(":"); if (hourMin.length == 2) { try { int h = Integer.parseInt(hourMin[0]); int m = Integer.parseInt(hourMin[1]); Calendar atCal = Calendar.getInstance(); atCal.set(0, 0, 0, h, m, 0); return new HourOfDayTimeProvider(atCal.getTime()); } catch (NumberFormatException ex) { } } return null; } /** * Creates time based on a date. * @param time base date * @return time */ private Calendar createAtDate(Date time) { Calendar timeCal = Calendar.getInstance(); timeCal.setTime(time); Calendar cal = Calendar.getInstance(); cal.set(0, 0, 0, timeCal.get(Calendar.HOUR_OF_DAY), timeCal.get(Calendar.MINUTE), 0); return cal; } } /** * Wait time provider for a day of the week. */ class DayOfWeekTimeProvider implements WaitTimeProvider { /** day of the week */ private DayOfWeek dayOfWeek; /** time of the day */ private Calendar at; /** * Creates instance of the provider. * @param dayOfWeek day of the week */ public DayOfWeekTimeProvider(DayOfWeek dayOfWeek) { this.dayOfWeek = dayOfWeek; this.at = createAtDate(); } /** * Creates instance of the provider. * @param dayOfWeek day of the week * @param time time of the day */ public DayOfWeekTimeProvider(DayOfWeek dayOfWeek, Date time) { this.dayOfWeek = dayOfWeek; this.at = createAtDate(time); } @Override public long getWaitTime(Date since) { Calendar cal = Calendar.getInstance(); cal.setTime(since); Calendar act = Calendar.getInstance(); int currentDayOfWeek = cal.get(Calendar.DAY_OF_WEEK); int diff = dayOfWeek.ordinal() - currentDayOfWeek; int span = diff >= 0 ? diff : 7 + diff; act.setTimeInMillis(cal.getTimeInMillis()); act.set(Calendar.HOUR_OF_DAY, at.get(Calendar.HOUR_OF_DAY)); act.set(Calendar.MINUTE, at.get(Calendar.MINUTE)); act.set(Calendar.SECOND, 0); act.set(Calendar.MILLISECOND, 0); if (!(span == 6 && act.after(cal))) { act.add(Calendar.DAY_OF_YEAR, 1); long msecToDayEnd = act.getTimeInMillis() - cal.getTimeInMillis(); long waitTime = span * Unit.day.getMilliseconds() + msecToDayEnd; return waitTime; } else { long waitTime = act.getTimeInMillis() - cal.getTimeInMillis(); return waitTime; } } /** * Parses value into wait time provider. * @param value value * @return provider or <code>null</code> if can not be parsed */ public static WaitTimeProvider parse(String value) { value = Val.chkStr(value); String[] elements = value.split("[ ]+"); if (elements.length == 1) { DayOfWeek dow = DayOfWeek.checkValueOf(elements[0]); if (dow != null) { return new DayOfWeekTimeProvider(dow); } } else if (elements.length == 2) { DayOfWeek dow = DayOfWeek.checkValueOf(elements[0]); String[] hourMin = elements[1].split(":"); if (dow != null && hourMin.length == 2) { try { int h = Integer.parseInt(hourMin[0]); int m = Integer.parseInt(hourMin[1]); Calendar atCal = Calendar.getInstance(); atCal.set(0, 0, 0, h, m, 0); return new DayOfWeekTimeProvider(dow, atCal.getTime()); } catch (NumberFormatException ex) { } } } return null; } /** * Creates empty time. * @return empty time */ private Calendar createAtDate() { Calendar cal = Calendar.getInstance(); cal.set(0, 0, 0, 0, 0, 0); return cal; } /** * Creates time based on a date. * @param time base date * @return time */ private Calendar createAtDate(Date time) { Calendar timeCal = Calendar.getInstance(); timeCal.setTime(time); Calendar cal = Calendar.getInstance(); cal.set(0, 0, 0, timeCal.get(Calendar.HOUR_OF_DAY), timeCal.get(Calendar.MINUTE), 0); return cal; } } /** * 'Snow ball' wait time provider. */ class SnowBallWaitTimeProvider implements WaitTimeProvider { /** array of providers */ private WaitTimeProvider [] providers; /** * Creates instance of the provider. * @param providers array of providers */ public SnowBallWaitTimeProvider(WaitTimeProvider [] providers) { this.providers = providers!=null? providers: new WaitTimeProvider []{}; } @Override public long getWaitTime(Date since) { long shortestWaitTime = -1; for (WaitTimeProvider p : providers) { long waitTime = p.getWaitTime(since); if (shortestWaitTime<0 || waitTime<shortestWaitTime) { shortestWaitTime = waitTime; } } return shortestWaitTime>=0? shortestWaitTime: 0; } /** * Parses array of time definitions into snow ball provider. * @param value array of time periods separated by comma (,) * @return snow ball provider or <code>null</code> value can not be parsed into snow ball provider */ public static WaitTimeProvider parse(String value) { value = Val.chkStr(value); String [] parts = value.split(","); ArrayList<WaitTimeProvider> providers = new ArrayList<WaitTimeProvider>(); for (String part : parts) { WaitTimeProvider wtp = WaitTimeProviderFactory.create(part); if (wtp!=null) providers.add(wtp); } return providers.size()>1? new SnowBallWaitTimeProvider(providers.toArray(new WaitTimeProvider[providers.size()])): null; } }