View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.core5.util;
29  
30  import java.text.ParseException;
31  import java.time.Instant;
32  import java.time.ZoneOffset;
33  import java.time.format.DateTimeFormatter;
34  import java.time.format.DateTimeFormatterBuilder;
35  import java.util.concurrent.TimeUnit;
36  
37  /**
38   * A deadline based on a UNIX time, the elapsed since 00:00:00 Coordinated Universal Time (UTC), Thursday, 1 January
39   * 1970.
40   *
41   * @since 5.0
42   */
43  public class Deadline {
44  
45      /**
46       * The format used for parsing and formatting dates.
47       */
48      public static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
49  
50      /**
51       * A special internal value that marks a deadline as the longest possible.
52       */
53      private static final long INTERNAL_MAX_VALUE = Long.MAX_VALUE;
54  
55      /**
56       * A special internal value that marks a deadline as the shortest possible.
57       */
58      private static final long INTERNAL_MIN_VALUE = 0;
59  
60      /**
61       * The maximum (longest-lived) deadline.
62       */
63      public static Deadline MAX_VALUE = new Deadline(INTERNAL_MAX_VALUE);
64  
65      /**
66       * The minimum (shortest-lived) deadline.
67       */
68      public static Deadline MIN_VALUE = new Deadline(INTERNAL_MIN_VALUE);
69  
70      private static final DateTimeFormatter DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()
71              .parseLenient()
72              .parseCaseInsensitive()
73              .appendPattern(DATE_FORMAT)
74              .toFormatter();
75  
76      /**
77       * Calculates a deadline with a given time in milliseconds plus a given time value. Non-positive time values
78       * represent an indefinite timeout without a deadline.
79       *
80       * @param timeMillis A time in UNIX milliseconds, usually the current time.
81       * @param timeValue time value to add to {@code timeMillis}.
82       * @return a deadline representing the current time plus the given time value.
83       */
84      public static Deadline calculate(final long timeMillis, final TimeValue timeValue) {
85          if (TimeValue.isPositive(timeValue)) {
86              // TODO handle unlikely overflow
87              final long deadline = timeMillis + timeValue.toMilliseconds();
88              return deadline < 0 ? Deadline.MAX_VALUE : Deadline.fromUnixMilliseconds(deadline);
89          }
90          return Deadline.MAX_VALUE;
91      }
92  
93      /**
94       * Calculates a deadline from now plus a given time value. Non-positive time values
95       * represent an indefinite timeout without a deadline.
96       *
97       * @param timeValue time value to add to {@code timeMillis}.
98       * @return a deadline representing the current time plus the given time value.
99       */
100     public static Deadline calculate(final TimeValue timeValue) {
101         return calculate(System.currentTimeMillis(), timeValue);
102     }
103 
104     /**
105      * Creates a deadline from a UNIX time in milliseconds.
106      *
107      * @param value a UNIX time in milliseconds.
108      * @return a new deadline.
109      */
110     public static Deadline fromUnixMilliseconds(final long value) {
111         if (value == INTERNAL_MAX_VALUE) {
112             return MAX_VALUE;
113         }
114         if (value == INTERNAL_MIN_VALUE) {
115             return MIN_VALUE;
116         }
117         return new Deadline(value);
118     }
119 
120     /**
121      * Creates a deadline from a string in the format {@value #DATE_FORMAT}.
122      *
123      * @param source a string in the format {@value #DATE_FORMAT}.
124      * @return a deadline from a string in the format {@value #DATE_FORMAT}.
125      * @throws ParseException if the specified source string cannot be parsed.
126      */
127     public static Deadline parse(final String source) throws ParseException {
128         if (source == null) {
129             return null;
130         }
131         final Instant instant = Instant.from(DATE_TIME_FORMATTER.parse(source));
132         return fromUnixMilliseconds(instant.toEpochMilli());
133     }
134 
135     private volatile boolean frozen;
136 
137     private volatile long lastCheck;
138 
139     /*
140      * Internal representation is a UNIX time.
141      */
142     private final long value;
143 
144     /**
145      * Constructs a new instance with the given UNIX time in milliseconds.
146      *
147      * @param deadlineMillis UNIX time in milliseconds.
148      */
149     private Deadline(final long deadlineMillis) {
150         super();
151         this.value = deadlineMillis;
152         setLastCheck();
153     }
154 
155     @Override
156     public boolean equals(final Object obj) {
157         // Only take into account the deadline value.
158         if (this == obj) {
159             return true;
160         }
161         if (obj == null) {
162             return false;
163         }
164         if (getClass() != obj.getClass()) {
165             return false;
166         }
167         final Deadline other = (Deadline) obj;
168         return value == other.value;
169     }
170 
171     @Override
172     public int hashCode() {
173         // Only take into account the deadline value.
174         return Long.hashCode(value);
175     }
176 
177     /**
178      * Formats this deadline.
179      *
180      * @param overdueTimeUnit the time unit to show how much over the deadline we are.
181      * @return a formatted string.
182      */
183     public String format(final TimeUnit overdueTimeUnit) {
184         return String.format("Deadline: %s, %s overdue", formatTarget(), TimeValue.of(remaining(), overdueTimeUnit));
185     }
186 
187     /**
188      * Formats the deadline value as a string in the format {@value #DATE_FORMAT}.
189      *
190      * @return a formatted string in the format {@value #DATE_FORMAT}.
191      */
192     public String formatTarget() {
193         return DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(value).atOffset(ZoneOffset.UTC));
194     }
195 
196     public Deadline freeze() {
197         frozen = true;
198         return this;
199     }
200 
201     /**
202      * Package private for testing.
203      *
204      * @return the last time we checked the current time.
205      */
206     long getLastCheck() {
207         return lastCheck;
208     }
209 
210     /**
211      * Gets the UNIX time deadline value.
212      *
213      * @return the UNIX time deadline value.
214      */
215     public long getValue() {
216         return value;
217     }
218 
219     /**
220      * Returns whether this deadline occurs before the given time in milliseconds.
221      *
222      * @param millis the time to compare.
223      * @return whether this deadline occurs before the given time in milliseconds.
224      */
225     public boolean isBefore(final long millis) {
226         return value < millis;
227     }
228 
229     /**
230      * Returns whether the deadline has expired.
231      *
232      * @return whether the deadline has expired.
233      */
234     public boolean isExpired() {
235         setLastCheck();
236         return value < this.lastCheck;
237     }
238 
239     /**
240      * Returns whether this deadline is the maximum deadline.
241      *
242      * @return whether this deadline is the maximum deadline.
243      */
244     public boolean isMax() {
245         return value == INTERNAL_MAX_VALUE;
246     }
247 
248     /**
249      * Returns whether this deadline is the minimum deadline.
250      *
251      * @return whether this deadline is the minimum deadline.
252      */
253     public boolean isMin() {
254         return value == INTERNAL_MIN_VALUE;
255     }
256 
257     /**
258      * Returns whether this deadline has not expired.
259      *
260      * @return whether this deadline has not expired.
261      */
262     public boolean isNotExpired() {
263         setLastCheck();
264         return value >= this.lastCheck;
265     }
266 
267     /**
268      * Returns the smaller of this and another {@code Deadline}.
269      *
270      * @param other another deadline.
271      * @return the smaller of {@code this} and {@code other}.
272      */
273     public Deadline min(final Deadline other) {
274         return value <= other.value ? this : other;
275     }
276 
277     /**
278      * Returns the difference in milliseconds between the deadline and now.
279      *
280      * @return the different in milliseconds between the deadline and now.
281      */
282     public long remaining() {
283         setLastCheck();
284         return value - lastCheck;
285     }
286 
287     /**
288      * Returns the difference as a TimeValue between the deadline and now.
289      *
290      * @return Returns the different as a TimeValue between the deadline and now.
291      */
292     public TimeValue remainingTimeValue() {
293         return TimeValue.of(remaining(), TimeUnit.MILLISECONDS);
294     }
295 
296     private void setLastCheck() {
297         if (!frozen) {
298             this.lastCheck = System.currentTimeMillis();
299         }}
300 
301     @Override
302     public String toString() {
303         return formatTarget();
304     }
305 
306 }