From 47563627b09b3ff9356aa89b5014dd3e80683d82 Mon Sep 17 00:00:00 2001 From: Jose Martin Rozanec Date: Fri, 26 Jun 2015 17:02:58 -0300 Subject: [PATCH] Issues #3 #12: create new CronDescriptor to enhance descriptions provided. --- .../descriptor/refactor/CronDescriptor.java | 94 +++++++++ .../descriptor/refactor/FieldDescriptor.java | 184 ++++++++++++++++++ .../field/constraint/FieldConstraints.java | 22 ++- .../resources/cron-descI18N_en.properties | 34 ++++ 4 files changed, 325 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/cronutils/descriptor/refactor/CronDescriptor.java create mode 100644 src/main/java/com/cronutils/descriptor/refactor/FieldDescriptor.java create mode 100644 src/main/resources/cron-descI18N_en.properties diff --git a/src/main/java/com/cronutils/descriptor/refactor/CronDescriptor.java b/src/main/java/com/cronutils/descriptor/refactor/CronDescriptor.java new file mode 100644 index 00000000..c15b5e43 --- /dev/null +++ b/src/main/java/com/cronutils/descriptor/refactor/CronDescriptor.java @@ -0,0 +1,94 @@ +package com.cronutils.descriptor.refactor; + +import com.cronutils.model.Cron; +import com.cronutils.model.field.CronField; +import com.cronutils.model.field.CronFieldName; +import com.cronutils.model.field.constraint.FieldConstraintsBuilder; +import com.cronutils.model.field.expression.Always; +import com.cronutils.model.field.expression.On; +import com.cronutils.model.field.value.IntegerFieldValue; +import com.google.common.collect.Range; +import org.apache.commons.lang3.Validate; + +import java.util.Locale; +import java.util.Map; +import java.util.ResourceBundle; + +class CronDescriptor { + public static final Locale DEFAULT_LOCALE = Locale.UK; + private static final String BUNDLE = "cron-descI18N"; + private ResourceBundle bundle; + + /** + * Constructor creating a descriptor for given Locale + * @param locale - Locale in which descriptions are given + */ + private CronDescriptor(Locale locale) { + bundle = ResourceBundle.getBundle(BUNDLE, locale); + } + + /** + * Default constructor. Considers Locale.UK as default locale + */ + private CronDescriptor() { + bundle = ResourceBundle.getBundle(BUNDLE, DEFAULT_LOCALE); + } + + /** + * Provide a description of given CronFieldParseResult list + * @param cron - Cron instance, never null + * if null, will throw NullPointerException + * @return description - String + */ + public String describe(Cron cron) { + Validate.notNull(cron, "Cron must not be null"); + Map expressions = cron.retrieveFieldsAsMap(); + FieldDescriptor descriptor = new FieldDescriptor(bundle); + StringBuilder builder = new StringBuilder(); + + CronField year; + Range yearRange; + if(expressions.containsKey(CronFieldName.YEAR)){ + year = expressions.get(CronFieldName.YEAR); + yearRange = cron.getCronDefinition().getFieldDefinition(CronFieldName.YEAR).getConstraints().getValidRange(); + } else { + year = new CronField(CronFieldName.YEAR, new Always(FieldConstraintsBuilder.instance().createConstraintsInstance())); + yearRange = Range.all(); + } + CronField month = expressions.get(CronFieldName.MONTH); + Range monthRange = cron.getCronDefinition().getFieldDefinition(CronFieldName.MONTH).getConstraints().getValidRange(); + CronField dom = expressions.get(CronFieldName.DAY_OF_MONTH); + Range domRange = cron.getCronDefinition().getFieldDefinition(CronFieldName.DAY_OF_MONTH).getConstraints().getValidRange(); + CronField dow = expressions.get(CronFieldName.DAY_OF_WEEK); + Range dowRange = cron.getCronDefinition().getFieldDefinition(CronFieldName.DAY_OF_WEEK).getConstraints().getValidRange(); + CronField hour = expressions.get(CronFieldName.HOUR); + Range hourRange = cron.getCronDefinition().getFieldDefinition(CronFieldName.HOUR).getConstraints().getValidRange(); + CronField minute = expressions.get(CronFieldName.MINUTE); + Range minuteRange = cron.getCronDefinition().getFieldDefinition(CronFieldName.MINUTE).getConstraints().getValidRange(); + CronField second; + Range secondRange; + if(expressions.containsKey(CronFieldName.SECOND)){ + second = expressions.get(CronFieldName.SECOND); + secondRange = cron.getCronDefinition().getFieldDefinition(CronFieldName.SECOND).getConstraints().getValidRange(); + } else { + second = new CronField( + CronFieldName.SECOND, + new On( + FieldConstraintsBuilder.instance() + .forField(CronFieldName.SECOND).createConstraintsInstance(), + new IntegerFieldValue(0)) + ); + secondRange = Range.all(); + } + + return new StringBuilder() + .append(descriptor.describe(null, second, secondRange)).append(" ") + .append(descriptor.describe(second, minute, minuteRange)).append(" ") + .append(descriptor.describe(minute, hour, hourRange)).append(" ") + .append(descriptor.describe(hour, dow, dowRange)).append(" ") + .append(descriptor.describe(dow, dom, domRange)).append(" ") + .append(descriptor.describe(dom, month, monthRange)).append(" ") + .append(descriptor.describe(month, year, yearRange)) + .toString().replaceAll("\\s+", " ").trim(); + } +} diff --git a/src/main/java/com/cronutils/descriptor/refactor/FieldDescriptor.java b/src/main/java/com/cronutils/descriptor/refactor/FieldDescriptor.java new file mode 100644 index 00000000..9199207c --- /dev/null +++ b/src/main/java/com/cronutils/descriptor/refactor/FieldDescriptor.java @@ -0,0 +1,184 @@ +package com.cronutils.descriptor.refactor; + +import com.cronutils.model.field.CronField; +import com.cronutils.model.field.expression.*; +import com.cronutils.model.field.value.IntegerFieldValue; +import com.cronutils.model.field.value.SpecialChar; +import com.cronutils.model.field.value.SpecialCharFieldValue; +import com.google.common.collect.Lists; +import com.google.common.collect.Range; +import com.google.common.collect.RangeSet; +import com.google.common.collect.TreeRangeSet; +import org.apache.commons.lang3.Validate; + +import java.text.MessageFormat; +import java.util.List; +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.Set; + +class FieldDescriptor { + protected ResourceBundle bundle; + private MessageFormat formatter; + + public FieldDescriptor(ResourceBundle bundle) { + this.bundle = bundle; + formatter = new MessageFormat("", Locale.US);//TODO extract + } + + protected String describe(CronField previous, CronField current, Range range) { + Validate.notNull(current, "CronField must not be null!"); + FieldExpression pexpression = null; + if (previous != null) { + pexpression = previous.getExpression(); + } + FieldExpression expression = current.getExpression(); + switch (current.getField()) { + case YEAR: + return describeYear(pexpression, expression, range); + case MONTH: + return describeMonth(pexpression, expression, range); + case DAY_OF_MONTH: + return describeDayOfMonth(pexpression, expression, range); + case DAY_OF_WEEK: + return describeDayOfWeek(pexpression, expression, range); + case HOUR: + return describeHour(pexpression, expression, range); + case MINUTE: + return describeMinute(pexpression, expression, range); + case SECOND: + return describeSecond(pexpression, expression, range); + } + return ""; + } + + protected String describeYear(FieldExpression previous, FieldExpression current, Range range) { + return describe(previous, current, range, "every_year", "between_x_and_y_year", "every_x_years", "on_x_year"); + } + + protected String describeMonth(FieldExpression previous, FieldExpression current, Range range) { + return describe(previous, current, range, "every_month", "between_x_and_y_month", "every_x_months", "on_x_month");//TODO add nominal transform + } + + protected String describeDayOfMonth(FieldExpression previous, FieldExpression current, Range range) { + return describe(previous, current, range, "every_dom", "between_x_and_y_dom", "every_x_dom", "on_x_dom"); + } + + protected String describeDayOfWeek(FieldExpression previous, FieldExpression current, Range range) { + return describe(previous, current, range, "every_dow", "between_x_and_y_dow", "every_x_dow", "on_x_dow"); + } + + protected String describeHour(FieldExpression previous, FieldExpression current, Range range) { + return describe(previous, current, range, "every_hour", "between_x_and_y_hour", "every_x_hour", "on_x_hour"); + } + + protected String describeMinute(FieldExpression previous, FieldExpression current, Range range) { + return describe(previous, current, range, "every_minute", "between_x_and_y_minute", "every_x_minute", "on_x_minute"); + } + + protected String describeSecond(FieldExpression previous, FieldExpression current, Range range) { + return describe(previous, current, range, "every_second", "between_x_and_y_second", "every_x_second", "on_x_second"); + } + + protected String describe(FieldExpression previous, FieldExpression current, Range range, + String always, String between, String everyX, String onX) { + if (current instanceof Always) { + return describeAlways(previous, current, always); + } + if (current instanceof Between) { + return describeBetween(previous, (Between)current, between); + } + if (current instanceof Every) { + if(((Every)current).getTime().getValue()==1) { + return describeAlways(previous, current, always); + }else { + return describeEveryX(previous, (Every)current, everyX); + } + } + if (current instanceof On) { + return describeOnX(previous, (On)current, onX); + } + if (current instanceof And) { + And and = (And)current; + StringBuilder builder = new StringBuilder(); + RangeSet ranges = TreeRangeSet.create(); + List onSpecialCharValues = Lists.newArrayList(); + for(FieldExpression expression : and.getExpressions()){ + if(expression instanceof On){ + On on = (On)expression; + if(SpecialChar.NONE.equals(on.getSpecialChar().getValue())){ + ranges.add(Range.closed(on.getTime().getValue(), on.getTime().getValue())); + }else{ + onSpecialCharValues.add(on.getSpecialChar().getValue()); + } + } + if(expression instanceof Between){ + Between betweenExp = (Between)expression; + if(betweenExp.getFrom() instanceof IntegerFieldValue && betweenExp.getTo() instanceof IntegerFieldValue){ + int from = ((IntegerFieldValue)betweenExp.getFrom()).getValue(); + int to = ((IntegerFieldValue)betweenExp.getTo()).getValue(); + int every = betweenExp.getEvery().getTime().getValue(); + if(every==1){ + ranges.add(Range.closed(from, to)); + }else{ + for(int j=from; j<=to; j+=every){ + ranges.add(Range.closed(j, j)); + } + } + } else { + SpecialChar specialChar; + int from; + if(betweenExp.getFrom() instanceof SpecialCharFieldValue){ + specialChar = ((SpecialCharFieldValue)betweenExp.getFrom()).getValue(); + from = ((IntegerFieldValue)betweenExp.getTo()).getValue(); + } else { + from = ((IntegerFieldValue)betweenExp.getFrom()).getValue(); + specialChar = ((SpecialCharFieldValue)betweenExp.getTo()).getValue(); + } + //TODO manage case where one of values is special char + } + } + if(expression instanceof Every){ + Every every = (Every)expression; + if(every.getTime().getValue()==1){ + return describeAlways(previous, current, always); + }else{ + //TODO we need the range, to calculate times in range + ranges.add(Range.closed(every.getTime().getValue(), every.getTime().getValue())); + } + } + Set> sortedDisconnectedRanges = ranges.asRanges(); + if(sortedDisconnectedRanges.size()==1 &&//TODO update Range.closed(1, 1) with correct value + range.equals(sortedDisconnectedRanges.iterator().next())){ + return always; + } + for(Range consolidatedRange : sortedDisconnectedRanges){ + //TODO describe ranges + } + } + } + return ""; + } + + protected String describeAlways(FieldExpression previous, FieldExpression current, String always){ + if(previous instanceof Always){ + return ""; + } + return bundle.getString(always); + } + + protected String describeBetween(FieldExpression previous, Between current, String between){ + formatter.applyPattern(bundle.getString(between)); + return formatter.format(new Object[]{current.getFrom().getValue(), current.getTo().getValue()}); + } + + protected String describeEveryX(FieldExpression previous, Every current, String everyX){ + formatter.applyPattern(bundle.getString(everyX)); + return formatter.format(new Object[]{current.getTime()}); + } + + protected String describeOnX(FieldExpression previous, On current, String onX){ + formatter.applyPattern(bundle.getString(onX)); + return formatter.format(new Object[]{current.getTime()}); + } +} diff --git a/src/main/java/com/cronutils/model/field/constraint/FieldConstraints.java b/src/main/java/com/cronutils/model/field/constraint/FieldConstraints.java index 986ed21e..89c4ac38 100644 --- a/src/main/java/com/cronutils/model/field/constraint/FieldConstraints.java +++ b/src/main/java/com/cronutils/model/field/constraint/FieldConstraints.java @@ -1,6 +1,7 @@ package com.cronutils.model.field.constraint; import com.cronutils.model.field.value.SpecialChar; +import com.google.common.collect.Range; import com.google.common.collect.Sets; import org.apache.commons.lang3.Validate; @@ -36,8 +37,7 @@ public class FieldConstraints { private Map stringMapping; private Map intMapping; private Set specialChars; - private int startRange; - private int endRange; + private Range range; private Pattern numsAndCharsPattern; private Pattern stringToIntKeysPattern; private Pattern lwPattern; @@ -59,8 +59,7 @@ public FieldConstraints(Map stringMapping, this.stringMapping = Collections.unmodifiableMap(Validate.notNull(stringMapping, "String mapping must not be null")); this.intMapping = Collections.unmodifiableMap(Validate.notNull(intMapping, "Integer mapping must not be null")); this.specialChars = Collections.unmodifiableSet(Validate.notNull(specialChars, "Special (non-standard) chars set must not be null")); - this.startRange = startRange; - this.endRange = endRange; + this.range = Range.closed(startRange, endRange); this.lwPattern = buildLWPattern(this.specialChars); this.stringToIntKeysPattern = buildStringToIntPattern(stringMapping.keySet()); this.numsAndCharsPattern = Pattern.compile("[#\\?/\\*0-9]"); @@ -108,7 +107,11 @@ public int validateInRange(int number) { if(isInRange(number)){ return number; } - throw new IllegalArgumentException(String.format("Number %s out of range [%s,%s]", number, startRange, endRange)); + throw new IllegalArgumentException( + String.format("Number %s out of range [%s,%s]", + number, range.lowerEndpoint(), range.upperEndpoint() + ) + ); } /** @@ -117,10 +120,7 @@ public int validateInRange(int number) { * @return - true if in range; false otherwise */ public boolean isInRange(int number) { - if (number >= startRange && number <= endRange) { - return true; - } - return false; + return range.contains(number); } /** @@ -153,6 +153,10 @@ public void validateAllCharsValid(String exp){ } } + public Range getValidRange(){ + return range; + } + String removeValidChars(String exp){ Matcher numsAndCharsMatcher = numsAndCharsPattern.matcher(exp); Matcher stringToIntKeysMatcher = stringToIntKeysPattern.matcher(numsAndCharsMatcher.replaceAll("")); diff --git a/src/main/resources/cron-descI18N_en.properties b/src/main/resources/cron-descI18N_en.properties new file mode 100644 index 00000000..c301ef16 --- /dev/null +++ b/src/main/resources/cron-descI18N_en.properties @@ -0,0 +1,34 @@ +every_year=every year +between_x_and_y_year=between {0} and {1} +every_x_year=every {0} years +on_x_year=on {0} + +every_month=every month +between_x_and_y_month=between {0} and {1} month +every_x_month=every {0} months +on_x_month=on {0} + +every_dom=every day of month +between_x_and_y_dom=between days {0} and {1} +every_x_dom=every {0} days of month +on_x_dom=on day {0} + +every_dow=every day of week +between_x_and_y_dow=between {0} and {1} +every_x_dow=every {0} days of week +on_x_dow=on {0} + +every_hour=every hour +between_x_and_y_hour=between {0} and {1} hours +every_x_hour=every {0} hours +on_x_hour=at {0} hs + +every_minute=every minute +between_x_and_y_minute=between {0} and {1} minutes +every_x_minute=between {0} minute +on_x_minute=on minute {0} + +every_second=every second +between_x_and_y_second=between {0} and {1} seconds +every_x_second=every {0} seconds +on_x_second=on {0} second \ No newline at end of file