Skip to content

Commit

Permalink
Issues #3 #12: create new CronDescriptor to enhance descriptions prov…
Browse files Browse the repository at this point in the history
…ided.
  • Loading branch information
jmrozanec committed Jun 26, 2015
1 parent 7f00e58 commit 4756362
Show file tree
Hide file tree
Showing 4 changed files with 325 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -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<CronFieldName, CronField> expressions = cron.retrieveFieldsAsMap();
FieldDescriptor descriptor = new FieldDescriptor(bundle);
StringBuilder builder = new StringBuilder();

CronField year;
Range<Integer> 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<Integer> monthRange = cron.getCronDefinition().getFieldDefinition(CronFieldName.MONTH).getConstraints().getValidRange();
CronField dom = expressions.get(CronFieldName.DAY_OF_MONTH);
Range<Integer> domRange = cron.getCronDefinition().getFieldDefinition(CronFieldName.DAY_OF_MONTH).getConstraints().getValidRange();
CronField dow = expressions.get(CronFieldName.DAY_OF_WEEK);
Range<Integer> dowRange = cron.getCronDefinition().getFieldDefinition(CronFieldName.DAY_OF_WEEK).getConstraints().getValidRange();
CronField hour = expressions.get(CronFieldName.HOUR);
Range<Integer> hourRange = cron.getCronDefinition().getFieldDefinition(CronFieldName.HOUR).getConstraints().getValidRange();
CronField minute = expressions.get(CronFieldName.MINUTE);
Range<Integer> minuteRange = cron.getCronDefinition().getFieldDefinition(CronFieldName.MINUTE).getConstraints().getValidRange();
CronField second;
Range<Integer> 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();
}
}
184 changes: 184 additions & 0 deletions src/main/java/com/cronutils/descriptor/refactor/FieldDescriptor.java
Original file line number Diff line number Diff line change
@@ -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<Integer> 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<Integer> 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<Integer> 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<Integer> 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<Integer> 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<Integer> 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<Integer> 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<Integer> 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<Integer> 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<Integer> ranges = TreeRangeSet.create();
List<SpecialChar> 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<Range<Integer>> 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()});
}
}
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -36,8 +37,7 @@ public class FieldConstraints {
private Map<String, Integer> stringMapping;
private Map<Integer, Integer> intMapping;
private Set<SpecialChar> specialChars;
private int startRange;
private int endRange;
private Range<Integer> range;
private Pattern numsAndCharsPattern;
private Pattern stringToIntKeysPattern;
private Pattern lwPattern;
Expand All @@ -59,8 +59,7 @@ public FieldConstraints(Map<String, Integer> 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]");
Expand Down Expand Up @@ -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()
)
);
}

/**
Expand All @@ -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);
}

/**
Expand Down Expand Up @@ -153,6 +153,10 @@ public void validateAllCharsValid(String exp){
}
}

public Range<Integer> getValidRange(){
return range;
}

String removeValidChars(String exp){
Matcher numsAndCharsMatcher = numsAndCharsPattern.matcher(exp);
Matcher stringToIntKeysMatcher = stringToIntKeysPattern.matcher(numsAndCharsMatcher.replaceAll(""));
Expand Down
34 changes: 34 additions & 0 deletions src/main/resources/cron-descI18N_en.properties
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 4756362

Please sign in to comment.