Java tutorial
/* * Copyright 2014 jmrozanec * * Licensed 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.cronutils.parser; import static com.cronutils.model.field.value.SpecialChar.HASH; import static com.cronutils.model.field.value.SpecialChar.L; import static com.cronutils.model.field.value.SpecialChar.LW; import static com.cronutils.model.field.value.SpecialChar.NONE; import static com.cronutils.model.field.value.SpecialChar.QUESTION_MARK; import static com.cronutils.model.field.value.SpecialChar.W; import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import com.cronutils.StringValidations; import com.cronutils.model.field.constraint.FieldConstraints; import com.cronutils.model.field.expression.Always; import com.cronutils.model.field.expression.And; import com.cronutils.model.field.expression.Between; import com.cronutils.model.field.expression.Every; import com.cronutils.model.field.expression.FieldExpression; import com.cronutils.model.field.expression.On; import com.cronutils.model.field.expression.QuestionMark; import com.cronutils.model.field.value.FieldValue; 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.annotations.VisibleForTesting; /** * Parses a field from a cron expression. */ public class FieldParser { private static final String SLASH = "/"; private static final String W_STRING = "W"; private static final String EMPTY_STRING = ""; private static final String LW_STRING = "LW"; private static final String HASH_TAG = "#"; private static final String L_STRING = "L"; private static final String QUESTION_MARK_STRING = "?"; private static final String ASTERISK = "*"; private static final char[] SPECIAL_CHARS_MINUS_STAR = new char[] { '/', '-', ',' };// universally supported private static final Pattern L_PATTERN = Pattern.compile("[0-9]L", Pattern.CASE_INSENSITIVE); private static final Pattern W_PATTERN = Pattern.compile("[0-9]W", Pattern.CASE_INSENSITIVE); private FieldConstraints fieldConstraints; public FieldParser(FieldConstraints constraints) { this.fieldConstraints = Validate.notNull(constraints, "FieldConstraints must not be null"); } /** * Parse given expression for a single cron field * * @param expression * - String * @return CronFieldExpression object that with interpretation of given String parameter */ public FieldExpression parse(String expression) { if (!StringUtils.containsAny(expression, SPECIAL_CHARS_MINUS_STAR)) { return noSpecialCharsNorStar(expression); } else { String[] array = expression.split(","); if (array.length > 1) { return commaSplitResult(array); } else { String[] betWeenArray = expression.split("-"); return dashSplitResult(expression, betWeenArray); } } } private FieldExpression dashSplitResult(String expression, String[] betWeenArray) { if (betWeenArray.length > 1) { return parseBetween(betWeenArray); } else { return slashSplit(expression, expression.split(SLASH)); } } private FieldExpression commaSplitResult(String[] array) { And and = new And(); for (String exp : array) { and.and(parse(exp)); } return and; } private FieldExpression slashSplit(String expression, String[] values) { if (values.length == 2) { String start = values[0]; String value = values[1]; return asteriskOrempty(start, value); } else if (values.length == 1) { throw new IllegalArgumentException("Missing steps for expression: " + expression); } else { throw new IllegalArgumentException("Invalid expression: " + expression); } } private FieldExpression asteriskOrempty(String start, String value) { String trimmedStart = start.trim(); if (ASTERISK.equals(trimmedStart) || EMPTY_STRING.equals(start.trim())) { return new Every(new IntegerFieldValue(Integer.parseInt(value))); } else { return new Every(new On(new IntegerFieldValue(Integer.parseInt(start))), new IntegerFieldValue(Integer.parseInt(value))); } } private FieldExpression noSpecialCharsNorStar(String expression) { if (ASTERISK.equals(expression)) {// all crons support asterisk return new Always(); } else { if (QUESTION_MARK_STRING.equals(expression)) { return new QuestionMark(); } return parseOn(expression); } } @VisibleForTesting FieldExpression parseBetween(String[] array) { if (array[1].contains(SLASH)) { String[] every = array[1].split(SLASH); return new Every(new Between(map(array[0]), map(every[0])), mapToIntegerFieldValue(every[1])); } else { return new Between(map(array[0]), map(array[1])); } } @VisibleForTesting On parseOn(String exp) { if (QUESTION_MARK_STRING.equals(exp)) { return parseOnWithQuestionMark(exp); } else if (exp.contains(HASH_TAG)) { return parseOnWithHash(exp); } else if (exp.contains(LW_STRING)) { return parseOnWithLW(exp); } else if (L_PATTERN.matcher(exp).find() || exp.equalsIgnoreCase(L_STRING)) { return parseOnWithL(exp); } else if (W_PATTERN.matcher(exp).find()) { return parseOnWithW(exp); } else { return new On(mapToIntegerFieldValue(exp), new SpecialCharFieldValue(NONE), new IntegerFieldValue(-1)); } } @VisibleForTesting On parseOnWithHash(String exp) { SpecialCharFieldValue specialChar = new SpecialCharFieldValue(HASH); String[] array = exp.split(HASH_TAG); IntegerFieldValue nth = mapToIntegerFieldValue(array[1]); if (array[0].isEmpty()) { throw new IllegalArgumentException("Time should be specified!"); } return new On(mapToIntegerFieldValue(array[0]), specialChar, nth); } @VisibleForTesting On parseOnWithQuestionMark(String exp) { SpecialCharFieldValue specialChar = new SpecialCharFieldValue(QUESTION_MARK); String questionMarkExpression = exp.replace(QUESTION_MARK_STRING, EMPTY_STRING); if (EMPTY_STRING.equals(questionMarkExpression)) { return new On(new IntegerFieldValue(-1), specialChar, new IntegerFieldValue(-1)); } else { throw new IllegalArgumentException(String.format("Expected: '?', found: %s", questionMarkExpression)); } } @VisibleForTesting On parseOnWithLW(String exp) { SpecialCharFieldValue specialChar = new SpecialCharFieldValue(LW); String lwExpression = exp.replace(LW_STRING, EMPTY_STRING); if (EMPTY_STRING.equals(lwExpression)) { return new On(new IntegerFieldValue(-1), specialChar, new IntegerFieldValue(-1)); } else { throw new IllegalArgumentException(String.format("Expected: LW, found: %s", lwExpression)); } } @VisibleForTesting On parseOnWithL(String exp) { SpecialCharFieldValue specialChar = new SpecialCharFieldValue(L); String lExpression = exp.replace(L_STRING, EMPTY_STRING); IntegerFieldValue time = new IntegerFieldValue(-1); if (!EMPTY_STRING.equals(lExpression)) { time = mapToIntegerFieldValue(lExpression); } return new On(time, specialChar, new IntegerFieldValue(-1)); } @VisibleForTesting On parseOnWithW(String exp) { return new On(mapToIntegerFieldValue(exp.replace(W_STRING, EMPTY_STRING)), new SpecialCharFieldValue(W), new IntegerFieldValue(-1)); } @VisibleForTesting IntegerFieldValue mapToIntegerFieldValue(String string) { try { return new IntegerFieldValue(intToInt(stringToInt(string))); } catch (NumberFormatException e) { throw new IllegalArgumentException( String.format("Invalid value. Expected some integer, found %s", string)); } } @VisibleForTesting FieldValue<?> map(String string) { for (SpecialChar sc : SpecialChar.values()) { if (sc.toString().equals(string)) { return new SpecialCharFieldValue(sc); } } return new IntegerFieldValue(stringToInt(string)); } /** * Maps string expression to integer. If no mapping is found, will try to parse String as Integer * * @param exp * - expression to be mapped * @return integer value for string expression */ int stringToInt(String exp) { Integer value = fieldConstraints.getStringMappingValue(exp); if (value != null) { return value; } else { try { return Integer.parseInt(exp); } catch (NumberFormatException e) { String invalidChars = new StringValidations(fieldConstraints).removeValidChars(exp); throw new IllegalArgumentException(String.format( "Invalid chars in expression! Expression: %s Invalid chars: %s", exp, invalidChars)); } } } /** * Maps integer values to another integer equivalence. Always consider mapping higher integers to lower once. Ex.: if 0 and 7 mean the * same, map 7 to 0. * * @param exp * - integer to be mapped * @return Mapping integer. If no mapping int is found, will return exp */ int intToInt(Integer exp) { Integer value = fieldConstraints.getIntMappingValue(exp); if (value != null) { return value; } return exp; } }