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 }