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         if (value == MAX_VALUE.value) {
185             return "No deadline (infinite)";
186         }
187         return String.format("Deadline: %s, %s overdue", formatTarget(), TimeValue.of(remaining(), overdueTimeUnit));
188     }
189 
190     /**
191      * Formats the deadline value as a string in the format {@value #DATE_FORMAT}.
192      *
193      * @return a formatted string in the format {@value #DATE_FORMAT}.
194      */
195     public String formatTarget() {
196         if (value == MAX_VALUE.value) {
197             return "(infinite)";
198         }
199         return DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(value).atOffset(ZoneOffset.UTC));
200     }
201 
202     public Deadline freeze() {
203         frozen = true;
204         return this;
205     }
206 
207     /**
208      * Package private for testing.
209      *
210      * @return the last time we checked the current time.
211      */
212     long getLastCheck() {
213         return lastCheck;
214     }
215 
216     /**
217      * Gets the UNIX time deadline value.
218      *
219      * @return the UNIX time deadline value.
220      */
221     public long getValue() {
222         return value;
223     }
224 
225     /**
226      * Returns whether this deadline occurs before the given time in milliseconds.
227      *
228      * @param millis the time to compare.
229      * @return whether this deadline occurs before the given time in milliseconds.
230      */
231     public boolean isBefore(final long millis) {
232         return value < millis;
233     }
234 
235     /**
236      * Returns whether the deadline has expired.
237      *
238      * @return whether the deadline has expired.
239      */
240     public boolean isExpired() {
241         setLastCheck();
242         return value < this.lastCheck;
243     }
244 
245     /**
246      * Returns whether this deadline is the maximum deadline.
247      *
248      * @return whether this deadline is the maximum deadline.
249      */
250     public boolean isMax() {
251         return value == INTERNAL_MAX_VALUE;
252     }
253 
254     /**
255      * Returns whether this deadline is the minimum deadline.
256      *
257      * @return whether this deadline is the minimum deadline.
258      */
259     public boolean isMin() {
260         return value == INTERNAL_MIN_VALUE;
261     }
262 
263     /**
264      * Returns whether this deadline has not expired.
265      *
266      * @return whether this deadline has not expired.
267      */
268     public boolean isNotExpired() {
269         setLastCheck();
270         return value >= this.lastCheck;
271     }
272 
273     /**
274      * Returns the smaller of this and another {@code Deadline}.
275      *
276      * @param other another deadline.
277      * @return the smaller of {@code this} and {@code other}.
278      */
279     public Deadline min(final Deadline other) {
280         return value <= other.value ? this : other;
281     }
282 
283     /**
284      * Returns the difference in milliseconds between the deadline and now.
285      *
286      * @return the different in milliseconds between the deadline and now.
287      */
288     public long remaining() {
289         setLastCheck();
290         return value - lastCheck;
291     }
292 
293     /**
294      * Returns the difference as a TimeValue between the deadline and now.
295      *
296      * @return Returns the different as a TimeValue between the deadline and now.
297      */
298     public TimeValue remainingTimeValue() {
299         return TimeValue.of(remaining(), TimeUnit.MILLISECONDS);
300     }
301 
302     private void setLastCheck() {
303         if (!frozen) {
304             this.lastCheck = System.currentTimeMillis();
305         }}
306 
307     @Override
308     public String toString() {
309         return formatTarget();
310     }
311 
312 }