1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tradefed.util; 18 19 import com.google.common.math.LongMath; 20 21 import java.util.regex.Matcher; 22 import java.util.regex.Pattern; 23 24 /** 25 * This is a sentinel type which wraps a {@code Long}. It exists solely as a hint to the options 26 * parsing machinery that a particular value should be parsed as if it were a string representing a 27 * time value. 28 * 29 * <p>We recommend using {@link java.time.Duration} instead. 30 */ 31 @SuppressWarnings({"serial", "ComparableType"}) // TODO: Fix me: https://errorprone.info/bugpattern/ComparableType 32 // We should not use Comparable<Long>, because 33 // Long.compareTo(TimeVal) != -TimeVal.compareTo(Long) 34 public class TimeVal extends Number implements Comparable<Long> { 35 private static final Pattern TIME_PATTERN = 36 Pattern.compile("(?i)" + // case insensitive 37 "(?:(?<d>\\d+)d)?" + // a number followed by "d" 38 "(?:(?<h>\\d+)h)?" + 39 "(?:(?<m>\\d+)m)?" + 40 "(?:(?<s>\\d+)s)?" + 41 "(?:(?<ms>\\d+)(?:ms)?)?"); // a number followed by "ms" 42 43 private Long mValue = null; 44 45 /** 46 * Constructs a newly allocated TimeVal object that represents the specified Long argument 47 */ TimeVal(Long value)48 public TimeVal(Long value) { 49 mValue = value; 50 } 51 52 /** 53 * Constructs a newly allocated TimeVal object that represents the <emph>timestamp</emph> 54 * indicated by the String parameter. The string is converted to a TimeVal in exactly the 55 * manner used by the {@link #fromString(String)} method. 56 */ TimeVal(String value)57 public TimeVal(String value) throws NumberFormatException { 58 mValue = fromString(value); 59 } 60 61 /** 62 * @return the wrapped {@code Long} value. 63 */ asLong()64 public Long asLong() { 65 return mValue; 66 } 67 68 /** 69 * Parses the string as a hierarchical time value 70 * <p /> 71 * The default unit is millis. The parser will accept {@code s} for seconds (1000 millis), 72 * {@code m} for minutes (60 seconds), {@code h} for hours (60 minutes), or {@code d} for days 73 * (24 hours). 74 * <p /> 75 * Units may be mixed and matched, so long as each unit appears at most once, and so long as 76 * all units which do appear are listed in decreasing order of scale. So, for instance, 77 * {@code h} may only appear before {@code m}, and may only appear after {@code d}. As a 78 * specific example, "1d2h3m4s5ms" would be a valid time value, as would "4" or "4ms". All 79 * embedded whitespace is discarded. 80 * <p /> 81 * Do note that this method rejects overflows. So the output number is guaranteed to be 82 * non-negative, and to fit within the {@code long} type. 83 */ fromString(String value)84 public static long fromString(String value) throws NumberFormatException { 85 if (value == null) { 86 throw new NumberFormatException("value is null"); 87 } 88 89 try { 90 value = value.replaceAll("\\s+", ""); 91 Matcher m = TIME_PATTERN.matcher(value); 92 if (m.matches()) { 93 // This works by, essentially, modifying the units of timeValue, from the 94 // largest supported unit, until we've dropped down to millis. 95 long timeValue = 0; 96 timeValue = val(m.group("d")); 97 98 // 1 day == 24 hours 99 timeValue = LongMath.checkedMultiply(timeValue, 24); 100 timeValue = LongMath.checkedAdd(timeValue, val(m.group("h"))); 101 102 // 1 hour == 60 minutes 103 timeValue = LongMath.checkedMultiply(timeValue, 60); 104 timeValue = LongMath.checkedAdd(timeValue, val(m.group("m"))); 105 106 // 1 hour == 60 seconds 107 timeValue = LongMath.checkedMultiply(timeValue, 60); 108 timeValue = LongMath.checkedAdd(timeValue, val(m.group("s"))); 109 110 // 1 second == 1000 millis 111 timeValue = LongMath.checkedMultiply(timeValue, 1000); 112 timeValue = LongMath.checkedAdd(timeValue, val(m.group("ms"))); 113 114 return timeValue; 115 } 116 } catch (ArithmeticException e) { 117 throw new NumberFormatException(String.format( 118 "Failed to parse value %s as a time value: %s", value, e.getMessage())); 119 } 120 121 throw new NumberFormatException( 122 String.format("Failed to parse value %s as a time value", value)); 123 } 124 val(String str)125 static long val(String str) throws NumberFormatException { 126 if (str == null) { 127 return 0; 128 } 129 130 long value = Long.parseLong(str); 131 return value; 132 } 133 134 135 // implementing interfaces 136 /** 137 * {@inheritDoc} 138 */ 139 @Override doubleValue()140 public double doubleValue() { 141 return mValue.doubleValue(); 142 } 143 144 /** 145 * {@inheritDoc} 146 */ 147 @Override floatValue()148 public float floatValue() { 149 return mValue.floatValue(); 150 } 151 152 /** 153 * {@inheritDoc} 154 */ 155 @Override intValue()156 public int intValue() { 157 return mValue.intValue(); 158 } 159 160 /** 161 * {@inheritDoc} 162 */ 163 @Override longValue()164 public long longValue() { 165 return mValue.longValue(); 166 } 167 168 /** 169 * {@inheritDoc} 170 */ 171 @Override compareTo(Long other)172 public int compareTo(Long other) { 173 return mValue.compareTo(other); 174 } 175 } 176