001/*
002 * Copyright 2018-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2018-2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2018-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.util;
037
038
039
040import java.io.Closeable;
041import java.util.concurrent.TimeUnit;
042import java.util.concurrent.TimeoutException;
043import java.util.concurrent.locks.ReentrantLock;
044
045import static com.unboundid.util.UtilityMessages.*;
046
047
048
049/**
050 * This class provides an implementation of a reentrant lock that can be used
051 * with the Java try-with-resources facility.  It does not implement the
052 * {@code java.util.concurrent.locks.Lock} interface in order to ensure that it
053 * can only be used through lock-with-resources mechanism, but it uses a
054 * {@code java.util.concurrent.locks.ReentrantLock} behind the scenes to provide
055 * its functionality.
056 * <BR><BR>
057 * <H2>Example</H2>
058 * The following example demonstrates how to use this lock using the Java
059 * try-with-resources facility:
060 * <PRE>
061 * // Wait for up to 5 seconds to acquire the lock.
062 * try (CloseableLock.Lock lock =
063 *           closeableLock.tryLock(5L, TimeUnit.SECONDS))
064 * {
065 *   // NOTE:  If you don't reference the lock object inside the try block, the
066 *   // compiler will issue a warning.
067 *   lock.avoidCompilerWarning();
068 *
069 *   // Do something while the lock is held.  The lock will automatically be
070 *   // released once code execution leaves this block.
071 * }
072 * catch (final InterruptedException e)
073 * {
074 *   // The thread was interrupted before the lock could be acquired.
075 * }
076 * catch (final TimeoutException)
077 * {
078 *   // The lock could not be acquired within the specified 5-second timeout.
079 * }
080 * </PRE>
081 */
082@Mutable()
083@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
084public final class CloseableLock
085{
086  // The {@code Closeable} object that will be returned by all of the methods
087  // used to acquire the lock.
088  @NotNull private final Lock lock;
089
090  // The reentrant lock that will be used to actually perform the locking.
091  @NotNull private final ReentrantLock reentrantLock;
092
093
094
095  /**
096   * Creates a new instance of this lock with a non-fair ordering policy.
097   */
098  public CloseableLock()
099  {
100    this(false);
101  }
102
103
104
105  /**
106   * Creates a new instance of this lock with the specified ordering policy.
107   *
108   * @param  fair  Indicates whether the lock should use fair ordering.  If
109   *               {@code true}, then if multiple threads are waiting on the
110   *               lock, then the one that has been waiting the longest is the
111   *               one that will get it.  If {@code false}, then no guarantee
112   *               will be made about the order.  Fair ordering can incur a
113   *               performance penalty.
114   */
115  public CloseableLock(final boolean fair)
116  {
117    reentrantLock = new ReentrantLock(fair);
118    lock = new Lock(reentrantLock);
119  }
120
121
122
123  /**
124   * Acquires this lock, blocking until the lock is available.
125   *
126   * @return  The {@link Lock} instance that may be used to perform the
127   *          unlock via the try-with-resources facility.
128   */
129  @NotNull()
130  public Lock lock()
131  {
132    reentrantLock.lock();
133    return lock;
134  }
135
136
137
138  /**
139   * Acquires this lock, blocking until the lock is available.
140   *
141   * @return  The {@link Lock} instance that may be used to perform the unlock
142   *          via the try-with-resources facility.
143   *
144   * @throws  InterruptedException  If the thread is interrupted while waiting
145   *                                to acquire the lock.
146   */
147  @NotNull()
148  public Lock lockInterruptibly()
149         throws InterruptedException
150  {
151    reentrantLock.lockInterruptibly();
152    return lock;
153  }
154
155
156
157  /**
158   * Tries to acquire the lock, waiting up to the specified length of time for
159   * it to become available.
160   *
161   * @param  waitTime  The maximum length of time to wait for the lock.  It must
162   *                   be greater than zero.
163   * @param  timeUnit  The time unit that should be used when evaluating the
164   *                   {@code waitTime} value.
165   *
166   * @return  The {@link Lock} instance that may be used to perform the unlock
167   *          via the try-with-resources facility.
168   *
169   * @throws  InterruptedException  If the thread is interrupted while waiting
170   *                                to acquire the lock.
171   *
172   * @throws  TimeoutException  If the lock could not be acquired within the
173   *                            specified length of time.
174   */
175  @NotNull()
176  public Lock tryLock(final long waitTime, @NotNull final TimeUnit timeUnit)
177         throws InterruptedException, TimeoutException
178  {
179    if (waitTime <= 0)
180    {
181      Validator.violation(
182           "CloseableLock.tryLock.waitTime must be greater than zero.  The " +
183                "provided value was " + waitTime);
184    }
185
186    if (reentrantLock.tryLock(waitTime, timeUnit))
187    {
188      return lock;
189    }
190    else
191    {
192      throw new TimeoutException(ERR_CLOSEABLE_LOCK_TRY_LOCK_TIMEOUT.get(
193           StaticUtils.millisToHumanReadableDuration(
194                timeUnit.toMillis(waitTime))));
195    }
196  }
197
198
199
200  /**
201   * Indicates whether this lock uses fair ordering.
202   *
203   * @return  {@code true} if this lock uses fair ordering, or {@code false} if
204   *          not.
205   */
206  public boolean isFair()
207  {
208    return reentrantLock.isFair();
209  }
210
211
212
213  /**
214   * Indicates whether this lock is currently held by any thread.
215   *
216   * @return  {@code true} if this lock is currently held by any thread, or
217   *          {@code false} if not.
218   */
219  public boolean isLocked()
220  {
221    return reentrantLock.isLocked();
222  }
223
224
225
226  /**
227   * Indicates whether this lock is currently held by the current thread.
228   *
229   * @return  {@code true} if this lock is currently held by the current thread,
230   *          or {@code false} if not.
231   */
232  public boolean isHeldByCurrentThread()
233  {
234    return reentrantLock.isHeldByCurrentThread();
235  }
236
237
238
239  /**
240   * Retrieves the number of holds that the current thread has on the lock.
241   *
242   * @return  The number of holds that the current thread has on the lock.
243   */
244  public int getHoldCount()
245  {
246    return reentrantLock.getHoldCount();
247  }
248
249
250
251  /**
252   * Indicates whether any threads are currently waiting to acquire this lock.
253   *
254   * @return  {@code true} if any threads are currently waiting to acquire this
255   *          lock, or {@code false} if not.
256   */
257  public boolean hasQueuedThreads()
258  {
259    return reentrantLock.hasQueuedThreads();
260  }
261
262
263
264  /**
265   * Indicates whether the specified thread is currently waiting to acquire this
266   * lock, or {@code false} if not.
267   *
268   * @param  thread  The thread for which to make the determination.  It must
269   *                 not be {@code null}.
270   *
271   * @return  {@code true} if the specified thread is currently waiting to
272   *          acquire this lock, or {@code false} if not.
273   */
274  public boolean hasQueuedThread(@NotNull final Thread thread)
275  {
276    Validator.ensureNotNull(thread);
277
278    return reentrantLock.hasQueuedThread(thread);
279  }
280
281
282
283  /**
284   * Retrieves an estimate of the number of threads currently waiting to acquire
285   * this lock.
286   *
287   * @return  An estimate of the number of threads currently waiting to acquire
288   *          this lock.
289   */
290  public int getQueueLength()
291  {
292    return reentrantLock.getQueueLength();
293  }
294
295
296
297  /**
298   * Retrieves a string representation of this lock.
299   *
300   * @return  A string representation of this lock.
301   */
302  @Override()
303  @NotNull()
304  public String toString()
305  {
306    return "CloseableLock(lock=" + reentrantLock.toString() + ')';
307  }
308
309
310
311  /**
312   * This class provides a {@code Closeable} implementation that may be used to
313   * unlock a {@link CloseableLock} via Java's try-with-resources
314   * facility.
315   */
316  public final class Lock
317         implements Closeable
318  {
319    // The associated reentrant lock.
320    @NotNull private final ReentrantLock lock;
321
322
323
324    /**
325     * Creates a new instance with the provided lock.
326     *
327     * @param  lock  The lock that will be unlocked when the [@link #close()}
328     *               method is called.  This must not be {@code null}.
329     */
330    private Lock(@NotNull final ReentrantLock lock)
331    {
332      this.lock = lock;
333    }
334
335
336
337    /**
338     * This method does nothing.  However, calling it inside a try block when
339     * used in the try-with-resources framework can help avoid a compiler
340     * warning that the JVM will give you if you don't reference the
341     * {@code Closeable} object inside the try block.
342     */
343    public void avoidCompilerWarning()
344    {
345      // No implementation is required.
346    }
347
348
349
350    /**
351     * Unlocks the associated lock.
352     */
353    @Override()
354    public void close()
355    {
356      lock.unlock();
357    }
358  }
359}