/** * * Copyright (c) 2009-2016 Freedomotic team * http://freedomotic.com * * This file is part of Freedomotic * * This Program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This Program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Freedomotic; see the file COPYING. If not, see * <http://www.gnu.org/licenses/>. */ package com.freedomotic.plugins; import java.util.Calendar; import java.util.GregorianCalendar; /** * Provides cron-like scheduling information. This class implements cron-like * definition of scheduling information. Various methods can be used to check * whether a timestamp matches the schedule or not. However, there is a slight * difference between cron and this class. Cron describes a match when either * the day of month and month or the day of week are met. This class requires * both to be met for a match. Also note that Calendar defines Sunday through * Saturday with 1 through 7 respectively * * @author RalphSchuster * http://techblog.ralph-schuster.eu/2008/02/01/handling-unix-cron-like-information-in-java/ */ public class CronSchedule { /** * Types being used. This array defines the types and their indices. */ protected static int[] TYPES = new int[]{Calendar.MINUTE, Calendar.HOUR_OF_DAY, Calendar.DAY_OF_MONTH, Calendar.MONTH, Calendar.DAY_OF_WEEK}; private AbstractTimeValue[][] timeValues = new AbstractTimeValue[TYPES.length][]; /** * Default constructor Constructor with all terms set to "*". */ public CronSchedule() { this("*", "*", "*", "*", "*"); } /** * Constructor with cron-style string initialization. The cron style is: * $minute $hour $dayOfMonth $month $dayOfWeek * * @param schedule */ public CronSchedule(String schedule) { set(schedule); } /** * Constructor with separate initialization values. * * @param min - minute definition * @param hour - hour definition * @param dom - day of month definition * @param mon - month definition * @param dow - day of week definition */ public CronSchedule(String min, String hour, String dom, String mon, String dow) { set(Calendar.MINUTE, min); set(Calendar.HOUR_OF_DAY, hour); set(Calendar.DAY_OF_MONTH, dom); set(Calendar.MONTH, mon); set(Calendar.DAY_OF_WEEK, dow); } /** * Sets the cron schedule. The cron style is: $minute $hour $dayOfMonth * $month $dayOfWeek The function will return any characters that follow the * cron definition * * @param schedule - cron-like schedule definition * @return characters following the cron definition. */ public String set(String schedule) { String[] parts = schedule.split(" ", TYPES.length + 1); if (parts.length < TYPES.length) { throw new IllegalArgumentException("Invalid cron format: " + schedule); } for (int i = 0; i < TYPES.length; i++) { set(getType(i), parts[i]); } return (parts.length > TYPES.length) ? parts[TYPES.length] : null; } /** * Sets the time values accordingly * * @param type - Calendar constant to define what values will be set * @param values - comma-separated list of definitions for that type */ public void set(int type, String values) { // Split the values String[] parts = values.split(","); AbstractTimeValue[] result = new AbstractTimeValue[parts.length]; // Iterate over entries for (int i = 0; i < parts.length; i++) { // Decide what time value is set and create it if (parts[i].indexOf('/') > 0) { result[i] = new TimeSteps(parts[i]); } else if (parts[i].indexOf('-') > 0) { result[i] = new TimeRange(parts[i]); } else if (parts[i].equals("*")) { result[i] = new TimeAll(); } else { result[i] = new SingleTimeValue(parts[i]); } } // Save the array set(type, result); } /** * Sets the values for a specific type * * @param type - Calendar constant defining the time type * @param values - values to be set */ protected void set(int type, AbstractTimeValue[] values) { timeValues[getIndex(type)] = values; } /** * Returns the values for a specific time type * * @param type - Calendar constant defining the type * @return time value definitions */ protected AbstractTimeValue[] getValues(int type) { return timeValues[getIndex(type)]; } /** * Returns the cron-like definition string for the given time value * * @param type - Calendar constant defining time type * @return cron-like definition */ public String get(int type) { AbstractTimeValue[] values = getValues(type); StringBuilder buff = new StringBuilder(); for (int i = 0; i < values.length; i++) { buff.append(",").append(values[i].toString()); } return buff.substring(1); } /** * Returns the cron-like definition of the schedule. * @return */ @Override public String toString() { StringBuilder buff = new StringBuilder(); for (int i = 0; i < TYPES.length; i++) { buff.append(" ").append(get(getType(i))); } return buff.toString().trim(); } /** * Checks whether given timestamp matches with defined schedule. This is * default check method. All criteria must be met including seconds to be 0. * * @param timeStamp - time in ms since Epoch time * @return true when schedule matches */ public boolean matches(long timeStamp) { return matches(getCalendar(timeStamp)); } /** * Checks whether given timestamp matches with defined schedule. This is * default check method. All criteria must be met including seconds to be 0. * * @param cal - calendar date * @return true when schedule matches */ public boolean matches(Calendar cal) { return isMinute(cal) && (cal.get(Calendar.SECOND) == 0); } /** * Checks whether given timestamp matches with defined schedule. This method * can be used when seconds are not relevant for matching. This is default * check method. * * @param timeStamp - time in ms since Epoch time * @return true when schedule matches */ public boolean isMinute(long timeStamp) { return isMinute(getCalendar(timeStamp)); } /** * Checks whether given calendar date matches with defined schedule. This * method can be used when seconds are not relevant for matching. * * @param cal - calendar date * @return true when schedule matches */ public boolean isMinute(Calendar cal) { return matches(Calendar.MINUTE, cal) && isHour(cal); } /** * Checks whether given timestamp matches with defined hour schedule. This * method can be used when minute definition is not relevant for matching. * * @param timeStamp * @param timestamp - time in ms since Epoch time * @return true when schedule matches */ public boolean isHour(long timeStamp) { return isHour(getCalendar(timeStamp)); } /** * Checks whether given calendar date matches with defined hour schedule. * This method can be used when minute definition is not relevant for * matching. * * @param cal - calendar date * @return true when schedule matches */ public boolean isHour(Calendar cal) { return matches(Calendar.HOUR_OF_DAY, cal) && isDay(cal); } /** * Checks whether given timestamp matches with defined day schedule. This * method can be used when minute and hour definitions are not relevant for * matching. * * @param timeStamp * @param timestamp - time in ms since Epoch time * @return true when schedule matches */ public boolean isDay(long timeStamp) { return isDay(getCalendar(timeStamp)); } /** * Checks whether given calendar date matches with defined day schedule. * This method can be used when minute and hour definitions are not relevant * for matching. * * @param cal - calendar date * @return true when schedule matches */ public boolean isDay(Calendar cal) { return matches(Calendar.DAY_OF_WEEK, cal) && matches(Calendar.DAY_OF_MONTH, cal) && matches(Calendar.MONTH, cal); } /** * Checks whether specific schedule definition matches against the given * calendar date. * * @param type - Calendar constant defining time type to check for * @param calendar - calendar representing the date to check * @return true when definition matches */ protected boolean matches(int type, Calendar calendar) { // get the definitions and the comparison value AbstractTimeValue[] defs = timeValues[getIndex(type)]; int value = calendar.get(type); // Any of the criteria must be met for (int i = 0; i < defs.length; i++) { if (defs[i].matches(value)) { return true; } } return false; } /** * Creates the calendar for a timestamp. * * @param timeStamp - timestamp * @return calendar */ protected Calendar getCalendar(long timeStamp) { Calendar rc = new GregorianCalendar(); rc.setTimeInMillis(timeStamp); return rc; } /** * Returns the type at the specified index * * @param index - index * @return Calendar constant of type */ protected static int getType(int index) { return TYPES[index]; } /** * Returns the index for the specified Calendar type. * * @param type - Calendar constant for type * @return internal index */ protected static int getIndex(int type) { for (int i = 0; i < TYPES.length; i++) { if (TYPES[i] == type) { return i; } } throw new IllegalArgumentException("No such time type: " + type); } /** * Base class for timing values. * * @author RalphSchuster */ public static abstract class AbstractTimeValue { /** * Returns true when given time value matches defined time. * * @param timeValue - time value to evaluate * @return true when time matches */ public abstract boolean matches(int timeValue); } /** * Represents a single time value, e.g. 9 * * @author RalphSchuster */ public static class SingleTimeValue extends AbstractTimeValue { private int value; /** * * @param value */ public SingleTimeValue(int value) { setValue(value); } /** * * @param value */ public SingleTimeValue(String value) { setValue(Integer.parseInt(value)); } /** * @return the value */ public int getValue() { return value; } /** * @param value the value to set */ public void setValue(int value) { this.value = value; } /** * Returns true when given time value matches defined value. * * @param timeValue - time value to evaluate * @return true when time matches */ public boolean matches(int timeValue) { return timeValue == getValue(); } /** * Returns cron-like string of this definition. * @return */ public String toString() { return "" + getValue(); } } /** * Represents a time range, e.g. 5-9 * * @author RalphSchuster */ public static class TimeRange extends AbstractTimeValue { private int startValue; private int endValue; /** * * @param startValue * @param endValue */ public TimeRange(int startValue, int endValue) { setStartValue(startValue); setEndValue(endValue); } /** * * @param range */ public TimeRange(String range) { int dashPos = range.indexOf('-'); setStartValue(Integer.parseInt(range.substring(0, dashPos))); setEndValue(Integer.parseInt(range.substring(dashPos + 1))); } /** * @return the endValue */ public int getEndValue() { return endValue; } /** * @param endValue the endValue to set */ public void setEndValue(int endValue) { this.endValue = endValue; } /** * @return the startValue */ public int getStartValue() { return startValue; } /** * @param startValue the startValue to set */ public void setStartValue(int startValue) { this.startValue = startValue; } /** * Returns true when given time value falls in range. * * @param timeValue - time value to evaluate * @return true when time falls in range */ public boolean matches(int timeValue) { return (getStartValue() <= timeValue) && (timeValue <= getEndValue()); } /** * Returns cron-like string of this definition. * @return */ public String toString() { return getStartValue() + "-" + getEndValue(); } } /** * Represents a time interval, e.g. 0-4/10 * * @author RalphSchuster */ public static class TimeSteps extends AbstractTimeValue { private AbstractTimeValue range; private int steps; /** * * @param range * @param steps */ public TimeSteps(AbstractTimeValue range, int steps) { setRange(range); setSteps(steps); } /** * * @param def */ public TimeSteps(String def) { int divPos = def.indexOf('/'); String r = def.substring(0, divPos); if (r.equals("*")) { setRange(new TimeAll()); } else if (r.indexOf('-') > 0) { setRange(new TimeRange(r)); } else { throw new IllegalArgumentException("Invalid range: " + def); } setSteps(Integer.parseInt(def.substring(divPos + 1))); } /** * Returns true when given time value matches the interval. * * @param timeValue - time value to evaluate * @return true when time matches the interval */ public boolean matches(int timeValue) { boolean rc = getRange().matches(timeValue); if (rc) { rc = (timeValue % getSteps()) == 0; } return rc; } /** * @return the range */ public AbstractTimeValue getRange() { return range; } /** * @param range the range to set */ public void setRange(AbstractTimeValue range) { this.range = range; } /** * @return the steps */ public int getSteps() { return steps; } /** * @param steps the steps to set */ public void setSteps(int steps) { this.steps = steps; } /** * Returns cron-like string of this definition. * @return */ public String toString() { return getRange() + "/" + getSteps(); } } /** * Represents the ALL time, *. * * @author RalphSchuster */ public static class TimeAll extends AbstractTimeValue { /** * */ public TimeAll() { } /** * Returns always true. * * @param timeValue - time value to evaluate * @return true */ public boolean matches(int timeValue) { return true; } /** * Returns cron-like string of this definition. * @return */ public String toString() { return "*"; } } }