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
26  * options parsing machinery that a particular value should be parsed as if it were a string
27  * representing a time value.
28  */
29 @SuppressWarnings("serial")
30 public class TimeVal extends Number implements Comparable<Long> {
31     private static final Pattern TIME_PATTERN =
32             Pattern.compile("(?i)" +  // case insensitive
33                     "(?:(?<d>\\d+)d)?" +  // a number followed by "d"
34                     "(?:(?<h>\\d+)h)?" +
35                     "(?:(?<m>\\d+)m)?" +
36                     "(?:(?<s>\\d+)s)?" +
37                     "(?:(?<ms>\\d+)(?:ms)?)?");  // a number followed by "ms"
38 
39     private Long mValue = null;
40 
41     /**
42      * Constructs a newly allocated TimeVal object that represents the specified Long argument
43      */
TimeVal(Long value)44     public TimeVal(Long value) {
45         mValue = value;
46     }
47 
48     /**
49      * Constructs a newly allocated TimeVal object that represents the <emph>timestamp</emph>
50      * indicated by the String parameter.  The string is converted to a TimeVal in exactly the
51      * manner used by the {@link #fromString(String)} method.
52      */
TimeVal(String value)53     public TimeVal(String value) throws NumberFormatException {
54         mValue = fromString(value);
55     }
56 
57     /**
58      * @return the wrapped {@code Long} value.
59      */
asLong()60     public Long asLong() {
61         return mValue;
62     }
63 
64     /**
65      * Parses the string as a hierarchical time value
66      * <p />
67      * The default unit is millis.  The parser will accept {@code s} for seconds (1000 millis),
68      * {@code m} for minutes (60 seconds), {@code h} for hours (60 minutes), or {@code d} for days
69      * (24 hours).
70      * <p />
71      * Units may be mixed and matched, so long as each unit appears at most once, and so long as
72      * all units which do appear are listed in decreasing order of scale.  So, for instance,
73      * {@code h} may only appear before {@code m}, and may only appear after {@code d}.  As a
74      * specific example, "1d2h3m4s5ms" would be a valid time value, as would "4" or "4ms".  All
75      * embedded whitespace is discarded.
76      * <p />
77      * Do note that this method rejects overflows.  So the output number is guaranteed to be
78      * non-negative, and to fit within the {@code long} type.
79      */
fromString(String value)80     public static long fromString(String value) throws NumberFormatException {
81         if (value == null) throw new NumberFormatException("value is null");
82 
83         try {
84             value = value.replaceAll("\\s+", "");
85             Matcher m = TIME_PATTERN.matcher(value);
86             if (m.matches()) {
87                 // This works by, essentially, modifying the units of timeValue, from the
88                 // largest supported unit, until we've dropped down to millis.
89                 long timeValue = 0;
90                 timeValue = val(m.group("d"));
91 
92                 // 1 day == 24 hours
93                 timeValue = LongMath.checkedMultiply(timeValue, 24);
94                 timeValue = LongMath.checkedAdd(timeValue, val(m.group("h")));
95 
96                 // 1 hour == 60 minutes
97                 timeValue = LongMath.checkedMultiply(timeValue, 60);
98                 timeValue = LongMath.checkedAdd(timeValue, val(m.group("m")));
99 
100                 // 1 hour == 60 seconds
101                 timeValue = LongMath.checkedMultiply(timeValue, 60);
102                 timeValue = LongMath.checkedAdd(timeValue, val(m.group("s")));
103 
104                 // 1 second == 1000 millis
105                 timeValue = LongMath.checkedMultiply(timeValue, 1000);
106                 timeValue = LongMath.checkedAdd(timeValue, val(m.group("ms")));
107 
108                 return timeValue;
109             }
110         } catch (ArithmeticException e) {
111             throw new NumberFormatException(String.format(
112                     "Failed to parse value %s as a time value: %s", value, e.getMessage()));
113         }
114 
115         throw new NumberFormatException(
116                 String.format("Failed to parse value %s as a time value", value));
117     }
118 
val(String str)119     static long val(String str) throws NumberFormatException {
120         if (str == null) return 0;
121 
122         Long value = Long.parseLong(str);
123         return value;
124     }
125 
126 
127     // implementing interfaces
128     /**
129      * {@inheritDoc}
130      */
131     @Override
doubleValue()132     public double doubleValue() {
133         return mValue.doubleValue();
134     }
135 
136     /**
137      * {@inheritDoc}
138      */
139     @Override
floatValue()140     public float floatValue() {
141         return mValue.floatValue();
142     }
143 
144     /**
145      * {@inheritDoc}
146      */
147     @Override
intValue()148     public int intValue() {
149         return mValue.intValue();
150     }
151 
152     /**
153      * {@inheritDoc}
154      */
155     @Override
longValue()156     public long longValue() {
157         return mValue.longValue();
158     }
159 
160     /**
161      * {@inheritDoc}
162      */
163     @Override
compareTo(Long other)164     public int compareTo(Long other) {
165         return mValue.compareTo(other);
166     }
167 }
168