diff --git a/bundles/core/org.eclipse.smarthome.core.test/src/test/java/org/eclipse/smarthome/core/scheduler/CronExpressionTest.java b/bundles/core/org.eclipse.smarthome.core.test/src/test/java/org/eclipse/smarthome/core/scheduler/CronExpressionTest.java index dc68cf03eb2..fbc058c17be 100644 --- a/bundles/core/org.eclipse.smarthome.core.test/src/test/java/org/eclipse/smarthome/core/scheduler/CronExpressionTest.java +++ b/bundles/core/org.eclipse.smarthome.core.test/src/test/java/org/eclipse/smarthome/core/scheduler/CronExpressionTest.java @@ -13,7 +13,6 @@ import java.util.Calendar; import java.util.Date; -import org.eclipse.smarthome.core.scheduler.CronExpression; import org.junit.Test; public class CronExpressionTest { @@ -63,4 +62,22 @@ public void getFinalTimeCheck() throws ParseException { assertEquals(checkDate, nextDate); } + + @Test + public void runForever() throws ParseException, InterruptedException { + + final CronExpression expression; + expression = new CronExpression("* * * * * ?"); + + Date nextDate = expression.getTimeAfter(Calendar.getInstance().getTime()); + int counter = 1; + + while (nextDate != null && counter <= 150) { + System.out.println("value " + counter + " is " + nextDate); + nextDate = expression.getTimeAfter(nextDate); + counter++; + } + + } + } \ No newline at end of file diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/scheduler/AbstractExpression.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/scheduler/AbstractExpression.java index fbaefe5c199..eaaf09cf5e8 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/scheduler/AbstractExpression.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/scheduler/AbstractExpression.java @@ -29,7 +29,7 @@ public abstract class AbstractExpression imple private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); - private int minimumCandidates = 10; + private int minimumCandidates = 1; private int maximumCandidates = 100; private String expression; @@ -207,22 +207,49 @@ protected void prune() { @Override public Date getTimeAfter(Date afterTime) { - if (getCandidates().isEmpty()) { + + Date currentStartDate = getStartDate(); + + if (hasFloatingStartDate()) { + try { + clearCandidates(); + setStartDate(afterTime); + } catch (IllegalArgumentException | ParseException e) { + logger.error("An exception occurred while setting the start date : '{}'", e.getMessage()); + } + } else if (getCandidates().isEmpty()) { try { setStartDate(afterTime); - parseExpression(expression); } catch (ParseException e) { - logger.error("An exception occurred while parsing the expression : '{}'", e.getMessage()); + logger.error("An exception occurred while setting the start date : '{}'", e.getMessage()); } } if (!getCandidates().isEmpty()) { - - Collections.sort(getCandidates()); - - for (Date candidate : getCandidates()) { - if (candidate.after(afterTime)) { - return candidate; + if (getCandidates().size() == 1) { + return getCandidates().get(0); + } else { + while (getCandidates().size() > 1) { + + Collections.sort(getCandidates()); + + Date newStartDate = null; + + try { + + for (Date candidate : getCandidates()) { + newStartDate = candidate; + if (candidate.after(afterTime)) { + setStartDate(currentStartDate); + return candidate; + } + } + + clearCandidates(); + setStartDate(newStartDate); + } catch (IllegalArgumentException | ParseException e) { + logger.error("An exception occurred while parsing the expression : '{}'", e.getMessage()); + } } } } @@ -232,6 +259,9 @@ public Date getTimeAfter(Date afterTime) { @Override public Date getFinalFireTime() { + + Date currentStartDate = getStartDate(); + if (getCandidates().isEmpty()) { try { parseExpression(getExpression()); @@ -240,11 +270,36 @@ public Date getFinalFireTime() { } } - if (getCandidates().isEmpty()) { - return null; + Date lastCandidate = null; + + if (!getCandidates().isEmpty()) { + if (getCandidates().size() == 1) { + lastCandidate = getCandidates().get(0); + } else { + + while (getCandidates().size() == maximumCandidates) { + Collections.sort(getCandidates()); + lastCandidate = getCandidates().get(getCandidates().size() - 1); + try { + clearCandidates(); + setStartDate(lastCandidate); + } catch (IllegalArgumentException | ParseException e) { + logger.error("An exception occurred while parsing the expression : '{}'", e.getMessage()); + } + + } + + lastCandidate = getCandidates().get(getCandidates().size() - 1); + + try { + setStartDate(currentStartDate); + } catch (IllegalArgumentException | ParseException e) { + logger.error("An exception occurred while setting the start date : '{}'", e.getMessage()); + } + } } - return getCandidates().get(getCandidates().size() - 1); + return lastCandidate; } /** @@ -275,6 +330,10 @@ protected void setCandidates(ArrayList candidates) { this.candidates = candidates; } + protected void clearCandidates() { + this.candidates = null; + } + public ArrayList getExpressionParts() { return expressionParts; } diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/scheduler/CronExpression.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/scheduler/CronExpression.java index d80226f39d4..6f2a3ae019e 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/scheduler/CronExpression.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/scheduler/CronExpression.java @@ -169,7 +169,7 @@ public CronExpression(final String expression, final Date startTime) throws Pars * @throws ParseException if the string expression cannot be parsed into a valid CronExpression. */ public CronExpression(final String expression, final Date startTime, final TimeZone zone) throws ParseException { - super(expression, " \t", startTime, zone, 10); + super(expression, " \t", startTime, zone, 0); } @Override @@ -1193,4 +1193,9 @@ public ArrayList apply(Date startDate, ArrayList candidates) { return candidates; } } + + @Override + public boolean hasFloatingStartDate() { + return true; + } } diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/scheduler/DateExpression.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/scheduler/DateExpression.java index 621452fae8e..a6dbdd0e731 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/scheduler/DateExpression.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/scheduler/DateExpression.java @@ -174,4 +174,9 @@ public int order() { } } + @Override + public boolean hasFloatingStartDate() { + return false; + } + } diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/scheduler/Expression.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/scheduler/Expression.java index 7c694fd7553..c22229103ab 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/scheduler/Expression.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/scheduler/Expression.java @@ -43,7 +43,7 @@ public interface Expression { /** * Returns the final time that the Expression will match. * - * @return the last date the expression will fire + * @return the last date the expression will fire, or null when the final time can not be calculated */ Date getFinalFireTime(); @@ -81,17 +81,25 @@ public interface Expression { /** * Returns the start date of the expression. * - * @return the start date. + * @return the start date, as from which the expression will be evaluated */ Date getStartDate(); /** - * Sets the start date of the rule. + * Sets the start date of the rule * * @param startTime the start date to set * @throws ParseException when the expression can not be parsed correctly after the start date was set */ void setStartDate(Date startTime) throws ParseException; + /** + * Indicates whether the expression does not need an explicit start date in order to be evaluated. 'infinite' style + * expression types, e.g. without an explicit start date part of their definition, like cron, should return true + * + * @return true, if the start date does not matter for evaluating the expression + */ + boolean hasFloatingStartDate(); + } \ No newline at end of file diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/scheduler/RecurrenceExpression.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/scheduler/RecurrenceExpression.java index 63a95e7a7d1..8cdff749f30 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/scheduler/RecurrenceExpression.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/scheduler/RecurrenceExpression.java @@ -326,7 +326,7 @@ * arrive at "every other year". Then, "BYMONTH=1" would be applied * to arrive at "every January, every other year". Then, "BYDAY=SU" * would be applied to arrive at "every Sunday in January, every - * other year". Then, "BYHOUR=8,9" would be applied to arrive at + * other year". Then, "BYHOUR=8,9" would be applied to arrive at * "every Sunday in January at 8 AM and 9 AM, every other year". * Then, "BYMINUTE=30" would be applied to arrive at "every Sunday in * January at 8:30 AM and 9:30 AM, every other year". Then, lacking @@ -551,23 +551,8 @@ public final Date getFinalFireTime() { if (!(isUntil || isCount)) { return null; } else { - - if (getCandidates().isEmpty()) { - try { - parseExpression(getExpression()); - } catch (ParseException e) { - logger.error("An exception occurred while parsing the expression : '{}'", e.getMessage()); - } - } - - if (!getCandidates().isEmpty()) { - - Collections.sort(getCandidates()); - - return getCandidates().get(getCandidates().size() - 1); - } + return super.getFinalFireTime(); } - return null; } @Override @@ -1847,4 +1832,9 @@ public int order() { } } + @Override + public boolean hasFloatingStartDate() { + return false; + } + }