package uws.job.parameters;
/*
* This file is part of UWSLibrary.
*
* UWSLibrary is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* UWSLibrary 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with UWSLibrary. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2016 - Astronomisches Rechen Institut (ARI)
*/
import java.text.ParseException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import uws.UWSException;
/**
* <p>
* Let controlling a duration parameter. Thus it is possible to set a default but also a minimum and a maximum value.
* Moreover you can indicate whether the value of the parameter can be modified by the user or not after initialization.
* </p>
*
* <p>This controller behaves like a {@link NumericParamController} EXCEPT on two points:</p>
* <ul>
* <li>Every given value is casted into a long value.
* This implies that any {@link Double} or {@link Float} values will be truncated/rounded.</li>
* <li>It is possible to check a {@link String} expressing the duration in a different unit.
* This string must be prefixed by a unit. See {@link #parseDuration(String)} (and its reverse operation {@link #toString(Long)})
* for more details.</li>
* </ul>
*
* @author Grégory Mantelet (ARI)
* @version 4.2 (06/2016)
* @since 4.2
*/
public class DurationParamController extends NumericParamController {
private static final long serialVersionUID = 1L;
/**
* Create a parameter controller for duration value with no restriction.
*
* <p>
* A default, minimum and/or maximum value can be set after creation using {@link #setDefault(Number)},
* {@link #setMinimum(Number)} and {@link #setMaximum(Number)}. By default this parameter can always be modified,
* but it can be forbidden using {@link #allowModification(boolean)}.
* </p>
*/
public DurationParamController(){
super();
}
/**
* <p>Create a controller for a parameter expressing a duration.
* The default and the maximum value are initialized with the given parameters (expressed in milliseconds).
* The third parameter allows also to forbid the modification of the parameter value by the user,
* if set to <i>false</i>.</p>
*
* <p>
* A default and/or maximum value can be modified after creation using {@link #setDefault(Number)}
* and {@link #setMaximum(Number)}. The flag telling whether this parameter can be modified by the user
* can be changed using {@link #allowModification(boolean)}.
* </p>
*
* <p><b>Important note:</b>
* Values given in this constructor MUST be expressed in milliseconds.
* </p>
*
* @param defaultValue Value (in ms) set by default to the parameter, when none is specified.
* @param minValue Minimum value (in ms) that can be set. If a smaller value is provided by the user, an exception will be thrown by {@link #check(Object)}.
* @param maxValue Maximum value (in ms) that can be set. If a bigger value is provided by the user, an exception will be thrown by {@link #check(Object)}.
* @param allowModification <i>true</i> to allow the user to modify this value when creating a job, <i>false</i> otherwise.
*/
public DurationParamController(final Long defaultValue, final Long minValue, final Long maxValue, final boolean allowModification){
super(defaultValue, minValue, maxValue, allowModification);
}
/**
* Cast the given value as a long value and call {@link NumericParamController#setDefault(Number)}.
*
* @see uws.job.parameters.NumericParamController#setMinimum(java.lang.Number)
*/
@Override
public void setDefault(final Number newDefaultValue){
super.setDefault((newDefaultValue == null) ? null : newDefaultValue.longValue());
}
/**
* Cast the given value as a long value and call {@link NumericParamController#setMinimum(Number)}.
*
* @see uws.job.parameters.NumericParamController#setMinimum(java.lang.Number)
*/
@Override
public void setMinimum(final Number newMinValue){
super.setMinimum((newMinValue == null) ? null : newMinValue.longValue());
}
/**
* Cast the given value as a long value and call {@link NumericParamController#setMaximum(Number)}.
*
* @see uws.job.parameters.NumericParamController#setMinimum(java.lang.Number)
*/
@Override
public void setMaximum(final Number newMaxValue){
super.setMaximum((newMaxValue == null) ? null : newMaxValue.longValue());
}
@Override
public Object check(final Object value) throws UWSException{
// If no value, return the default one:
if (value == null)
return getDefault();
// Otherwise, parse the given numeric value:
long numVal;
if (value instanceof Number)
numVal = ((Number)value).longValue();
else if (value instanceof String){
try{
numVal = parseDuration((String)value);
}catch(ParseException pe){
throw new UWSException(UWSException.BAD_REQUEST, "Wrong format for a duration parameter: \"" + value + "\"! It should be a positive long value between " + (minValue == null ? 0 : toString((Long)minValue)) + " and " + (maxValue == null ? toString(Long.MAX_VALUE) : toString((Long)maxValue)) + " (Default value: " + (defaultValue == null ? "none" : toString((Long)defaultValue)) + "). This value may be followed by a unit among: milliseconds (ms ; the default), seconds (s), minutes (min,m), hours (h), days (D), weeks (W), months (M) or years (Y).");
}
}else
throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "Wrong type for a duration parameter: class \"" + value.getClass().getName() + "\"! It should be a positive long value or a string containing only a positive long value eventually followed by a unit.");
// If the value is SMALLER than the minimum, the minimum value will be returned:
if (minValue != null && numVal < minValue.doubleValue())
return minValue;
// If the value is BIGGER than the maximum, the maximum value will be returned:
else if (maxValue != null && numVal > maxValue.doubleValue())
return maxValue;
// Otherwise, return the parsed number:
else
return numVal;
}
/* **************** */
/* DURATION PARSING */
/* **************** */
/** Multiplication factor between milliseconds and seconds.
* <p>A second is here defined as 1000 milliseconds. So the value is computed as follows: 1000.</p> */
protected final static long MULT_SEC = 1000;
/** Multiplication factor between milliseconds and minutes.
* <p>A minute is here defined as 60 seconds. So the value is computed as follows: {@link #MULT_SEC}*60.</p> */
protected final static long MULT_MIN = 60000;
/** Multiplication factor between milliseconds and hours.
* <p>An hour is here defined as 60 minutes. So the value is computed as follows: {@link #MULT_MINUTES}*60.</p> */
protected final static long MULT_HOURS = 3600000;
/** Multiplication factor between milliseconds and days.
* <p>A day is here defined as 24 hours. So the value is computed as follows: {@link #MULT_HOURS}*24.</p> */
protected final static long MULT_DAYS = 86400000;
/** Multiplication factor between milliseconds and weeks.
* <p>A week is here defined as 7 days. So the value is computed as follows: {@link #MULT_DAYS}*7.</p> */
protected final static long MULT_WEEKS = 604800000;
/** Multiplication factor between milliseconds and months.
* <p>A month is here defined as 30 days. So the value is computed as follows: {@link #MULT_DAYS}*30.</p> */
protected final static long MULT_MONTHS = 2592000000l;
/** Multiplication factor between milliseconds and years.
* <p>A year is here defined as 365 days. So the value is computed as follows: {@link #MULT_DAYS}*365.</p> */
protected final static long MULT_YEARS = 31536000000l;
/** Regular Expression of all string allowed to mean MILLISECONDS.
* <p><b>Important:</b> opening and closing brackets are omitted here for a better integration
* in the duration string's regular expression (see {@link #PATTERN_DURATION} ).</p> */
protected static String REGEXP_MS = "milliseconds|ms";
/** Regular Expression of all string allowed to mean SECONDS.
* <p><b>Important:</b> opening and closing brackets are omitted here for a better integration
* in the duration string's regular expression (see {@link #PATTERN_DURATION} ).</p> */
protected static String REGEXP_SEC = "seconds|sec|s";
/** Regular Expression of all string allowed to mean MINUTES.
* <p><b>Important:</b> opening and closing brackets are omitted here for a better integration
* in the duration string's regular expression (see {@link #PATTERN_DURATION} ).</p> */
protected static String REGEXP_MIN = "min|minutes|m";
/** Regular Expression of all string allowed to mean HOURS.
* <p><b>Important:</b> opening and closing brackets are omitted here for a better integration
* in the duration string's regular expression (see {@link #PATTERN_DURATION} ).</p> */
protected static String REGEXP_HOURS = "hours|h";
/** Regular Expression of all string allowed to mean DAYS.
* <p><b>Important:</b> opening and closing brackets are omitted here for a better integration
* in the duration string's regular expression (see {@link #PATTERN_DURATION} ).</p> */
protected static String REGEXP_DAYS = "days|D";
/** Regular Expression of all string allowed to mean WEEKS.
* <p><b>Important:</b> opening and closing brackets are omitted here for a better integration
* in the duration string's regular expression (see {@link #PATTERN_DURATION} ).</p> */
protected static String REGEXP_WEEKS = "weeks|W";
/** Regular Expression of all string allowed to mean MONTHS.
* <p><b>Important:</b> opening and closing brackets are omitted here for a better integration
* in the duration string's regular expression (see {@link #PATTERN_DURATION} ).</p> */
protected static String REGEXP_MONTHS = "months|M";
/** Regular Expression of all string allowed to mean YEARS.
* <p><b>Important:</b> opening and closing brackets are omitted here for a better integration
* in the duration string's regular expression (see {@link #PATTERN_DURATION} ).</p> */
protected static String REGEXP_YEARS = "years|Y";
/** Pattern created with the Regular Expression of a valid duration string.
* <p>
* Such string MUST be a positive integer/long value eventually suffixed by a unit.
* Allowed unit strings are the following:
* </p>
* <ul>
* <li>milliseconds, ms</li>
* <li>seconds, sec, s</li>
* <li>minutes, min, m</li>
* <li>hours, h</li>
* <li>days, D</li>
* <li>weeks, W</li>
* <li>months, M</li>
* <li>years, Y</li>
* </ul>
*
* <p><b>Important:</b>
* Units are case <b>sensitive</b>!
* </p>
*
* <p><i>Note:
* Space characters are ignored only if leading or trailing the whole string,
* or if between the duration and its unit.
* </i></p> */
protected static Pattern PATTERN_DURATION = Pattern.compile("\\s*([0-9]+)\\s*(" + REGEXP_MS + "|" + REGEXP_SEC + "|" + REGEXP_MIN + "|" + REGEXP_HOURS + "|" + REGEXP_DAYS + "|" + REGEXP_WEEKS + "|" + REGEXP_MONTHS + "|" + REGEXP_YEARS + ")?\\s*");
/**
* Parse the given duration string.
*
* <p>
* Such string MUST be a positive integer/long value eventually suffixed by a unit.
* Allowed unit strings are the following:
* </p>
* <ul>
* <li>milliseconds, ms</li>
* <li>seconds, sec, s</li>
* <li>minutes, min, m</li>
* <li>hours, h</li>
* <li>days, D</li>
* <li>weeks, W</li>
* <li>months, M</li>
* <li>years, Y</li>
* </ul>
*
* <p><b>Important:</b>
* Units are case <b>sensitive</b>!
* </p>
*
* <p><i>Note:
* Space characters are ignored only if leading or trailing the whole string,
* or if between the duration and its unit.
* </i></p>
*
* @param duration The duration string.
*
* @return The parsed duration converted into milliseconds,
* or <code>-1</code> if the given string is <code>null</code> or negative.
*
* @throws ParseException If the given string is using an unknown unit string,
* or if the string does not start digits.
*
* @see #toString(Long)
*/
public long parseDuration(final String duration) throws ParseException{
if (duration == null || duration.matches("\\s*-.*"))
return -1;
Matcher matcher = PATTERN_DURATION.matcher(duration);
if (!matcher.matches())
throw new ParseException("Unexpected format for a duration: \"" + duration + "\"! Cause: it does not match the following Regular Expression: " + PATTERN_DURATION.pattern(), 0);
try{
// Extract the numerical value:
long numDuration = Long.parseLong(matcher.group(1));
// Apply any multiplication to this duration:
String unit = matcher.group(2);
if (unit == null || unit.length() == 0 || unit.matches("(" + REGEXP_MS + ")"))
return numDuration;
else if (unit.matches("(" + REGEXP_SEC + ")"))
return numDuration * MULT_SEC;
else if (unit.matches("(" + REGEXP_MIN + ")"))
return numDuration * MULT_MIN;
else if (unit.matches("(" + REGEXP_HOURS + ")"))
return numDuration * MULT_HOURS;
else if (unit.matches("(" + REGEXP_DAYS + ")"))
return numDuration * MULT_DAYS;
else if (unit.matches("(" + REGEXP_WEEKS + ")"))
return numDuration * MULT_WEEKS;
else if (unit.matches("(" + REGEXP_MONTHS + ")"))
return numDuration * MULT_MONTHS;
else if (unit.matches("(" + REGEXP_YEARS + ")"))
return numDuration * MULT_YEARS;
}catch(Exception ex){
throw new ParseException("Unexpected format for a duration: \"" + duration + "\"! Cause: " + ex.getMessage(), matcher.regionStart());
}
return -1;
}
/**
* Convert a duration value (expressed in milliseconds) into the best human readable unit value.
*
* @param duration A duration in milliseconds.
*
* @return An empty string if the given duration is <code>null</code>,
* or a string expressing the given duration in the best integer value with a unit suffix.
*
* @see #parseDuration(String)
*/
public String toString(Long duration){
if (duration == null)
return "";
if (duration == 0)
return "0ms";
else if (duration % MULT_YEARS == 0)
return duration / MULT_YEARS + "Y";
else if (duration % MULT_MONTHS == 0)
return duration / MULT_MONTHS + "M";
else if (duration % MULT_WEEKS == 0)
return duration / MULT_WEEKS + "W";
else if (duration % MULT_DAYS == 0)
return duration / MULT_DAYS + "D";
else if (duration % MULT_HOURS == 0)
return duration / MULT_HOURS + "h";
else if (duration % MULT_MIN == 0)
return duration / MULT_MIN + "m";
else if (duration % MULT_SEC == 0)
return duration / MULT_SEC + "s";
else
return duration + "ms";
}
}