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