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