    001    // Copyright 2007, 2008, 2010 The Apache Software Foundation
    002    //
    003    // Licensed under the Apache License, Version 2.0 (the "License");
    004    // you may not use this file except in compliance with the License.
    005    // You may obtain a copy of the License at
    006    //
    007    // http://www.apache.org/licenses/LICENSE-2.0
    008    //
    009    // Unless required by applicable law or agreed to in writing, software
    010    // distributed under the License is distributed on an "AS IS" BASIS,
    011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012    // See the License for the specific language governing permissions and
    013    // limitations under the License.
    014
    015    package org.apache.tapestry5.ioc.util;
    016
    017    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
    018
    019    import java.util.Map;
    020    import java.util.regex.Matcher;
    021    import java.util.regex.Pattern;
    022
    023    /**
    024     * Used to represent a period of time, specifically as a configuration value. This is often used to specify timeouts.
    025     * <p/>
    026     * TimePeriods are parsed from strings.
    027     * <p/>
    028     * The string specifys a number of terms. The values of all the terms are summed together to form the total time period.
    029     * Each term consists of a number followed by a unit. Units (from largest to smallest) are: <dl> <dt>y <dd>year <dt>d
    030     * <dd>day <dt>h <dd>hour <dt>m <dd>minute <dt>s <dd>second <dt>ms <dd>millisecond </dl> <p>  Example: "2 h 30 m". By
    031     * convention, terms are specified largest to smallest.  A term without a unit is assumed to be milliseconds.  Units are
    032     * case insensitive ("h" or "H" are treated the same).
    033     */
    034    public class TimeInterval
    035    {
    036        private static final Map<String, Long> UNITS = CollectionFactory.newCaseInsensitiveMap();
    037
    038        private static final long MILLISECOND = 1000l;
    039
    040        static
    041        {
    042            UNITS.put("ms", 1l);
    043            UNITS.put("s", MILLISECOND);
    044            UNITS.put("m", 60 * MILLISECOND);
    045            UNITS.put("h", 60 * UNITS.get("m"));
    046            UNITS.put("d", 24 * UNITS.get("h"));
    047            UNITS.put("y", 365 * UNITS.get("d"));
    048        }
    049
    050        /**
    051         * The unit keys, sorted in descending order.
    052         */
    053        private static final String[] UNIT_KEYS =
    054        { "y", "d", "h", "m", "s", "ms" };
    055
    056        private static final Pattern PATTERN = Pattern.compile("\\s*(\\d+)\\s*([a-z]*)", Pattern.CASE_INSENSITIVE);
    057
    058        private final long milliseconds;
    059
    060        /**
    061         * Creates a TimeInterval for a string.
    062         *
    063         * @param input
    064         *            the string specifying the amount of time in the period
    065         */
    066        public TimeInterval(String input)
    067        {
    068            this(parseMilliseconds(input));
    069        }
    070
    071        public TimeInterval(long milliseconds)
    072        {
    073            this.milliseconds = milliseconds;
    074        }
    075
    076        public long milliseconds()
    077        {
    078            return milliseconds;
    079        }
    080
    081        public long seconds()
    082        {
    083            return milliseconds / MILLISECOND;
    084        }
    085
    086        /**
    087         * Converts the milliseconds back into a string (compatible with {@link #TimeInterval(String)}).
    088         *
    089         * @since 5.2.0
    090         */
    091        public String toDescription()
    092        {
    093            StringBuilder builder = new StringBuilder();
    094
    095            String sep = "";
    096
    097            long remainder = milliseconds;
    098
    099            for (String key : UNIT_KEYS)
    100            {
    101                if (remainder == 0)
    102                    break;
    103
    104                long value = UNITS.get(key);
    105
    106                long units = remainder / value;
    107
    108                if (units > 0)
    109                {
    110                    builder.append(sep);
    111                    builder.append(units);
    112                    builder.append(key);
    113
    114                    sep = " ";
    115
    116                    remainder = remainder % value;
    117                }
    118            }
    119
    120            return builder.toString();
    121        }
    122
    123        static long parseMilliseconds(String input)
    124        {
    125            long milliseconds = 0l;
    126
    127            Matcher matcher = PATTERN.matcher(input);
    128
    129            matcher.useAnchoringBounds(true);
    130
    131            // TODO: Notice non matching characters and reject input, including at end
    132
    133            int lastMatchEnd = -1;
    134
    135            while (matcher.find())
    136            {
    137                int start = matcher.start();
    138
    139                if (lastMatchEnd + 1 < start)
    140                {
    141                    String invalid = input.substring(lastMatchEnd + 1, start);
    142                    throw new RuntimeException(UtilMessages.invalidTimeIntervalInput(invalid, input));
    143                }
    144
    145                lastMatchEnd = matcher.end();
    146
    147                long count = Long.parseLong(matcher.group(1));
    148                String units = matcher.group(2);
    149
    150                if (units.length() == 0)
    151                {
    152                    milliseconds += count;
    153                    continue;
    154                }
    155
    156                Long unitValue = UNITS.get(units);
    157
    158                if (unitValue == null)
    159                    throw new RuntimeException(UtilMessages.invalidTimeIntervalUnit(units, input, UNITS.keySet()));
    160
    161                milliseconds += count * unitValue;
    162            }
    163
    164            if (lastMatchEnd + 1 < input.length())
    165            {
    166                String invalid = input.substring(lastMatchEnd + 1);
    167                throw new RuntimeException(UtilMessages.invalidTimeIntervalInput(invalid, input));
    168            }
    169
    170            return milliseconds;
    171        }
    172
    173        @Override
    174        public String toString()
    175        {
    176            return String.format("TimeInterval[%d ms]", milliseconds);
    177        }
    178
    179        @Override
    180        public boolean equals(Object obj)
    181        {
    182            if (obj == null)
    183                return false;
    184
    185            if (obj instanceof TimeInterval)
    186            {
    187                TimeInterval tp = (TimeInterval) obj;
    188
    189                return milliseconds == tp.milliseconds;
    190            }
    191
    192            return false;
    193        }
    194    }