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