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 }