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    }