001/*
002 * Copyright 2018-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2018-2020 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-2020 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  private final Lock lock;
089
090  // The reentrant lock that will be used to actually perform the locking.
091  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  public Lock lock()
130  {
131    reentrantLock.lock();
132    return lock;
133  }
134
135
136
137  /**
138   * Acquires this lock, blocking until the lock is available.
139   *
140   * @return  The {@link Lock} instance that may be used to perform the unlock
141   *          via the try-with-resources facility.
142   *
143   * @throws  InterruptedException  If the thread is interrupted while waiting
144   *                                to acquire the lock.
145   */
146  public Lock lockInterruptibly()
147         throws InterruptedException
148  {
149    reentrantLock.lockInterruptibly();
150    return lock;
151  }
152
153
154
155  /**
156   * Tries to acquire the lock, waiting up to the specified length of time for
157   * it to become available.
158   *
159   * @param  waitTime  The maximum length of time to wait for the lock.  It must
160   *                   be greater than zero.
161   * @param  timeUnit  The time unit that should be used when evaluating the
162   *                   {@code waitTime} value.
163   *
164   * @return  The {@link Lock} instance that may be used to perform the unlock
165   *          via the try-with-resources facility.
166   *
167   * @throws  InterruptedException  If the thread is interrupted while waiting
168   *                                to acquire the lock.
169   *
170   * @throws  TimeoutException  If the lock could not be acquired within the
171   *                            specified length of time.
172   */
173  public Lock tryLock(final long waitTime, final TimeUnit timeUnit)
174         throws InterruptedException, TimeoutException
175  {
176    if (waitTime <= 0)
177    {
178      Validator.violation(
179           "CloseableLock.tryLock.waitTime must be greater than zero.  The " +
180                "provided value was " + waitTime);
181    }
182
183    if (reentrantLock.tryLock(waitTime, timeUnit))
184    {
185      return lock;
186    }
187    else
188    {
189      throw new TimeoutException(ERR_CLOSEABLE_LOCK_TRY_LOCK_TIMEOUT.get(
190           StaticUtils.millisToHumanReadableDuration(
191                timeUnit.toMillis(waitTime))));
192    }
193  }
194
195
196
197  /**
198   * Indicates whether this lock uses fair ordering.
199   *
200   * @return  {@code true} if this lock uses fair ordering, or {@code false} if
201   *          not.
202   */
203  public boolean isFair()
204  {
205    return reentrantLock.isFair();
206  }
207
208
209
210  /**
211   * Indicates whether this lock is currently held by any thread.
212   *
213   * @return  {@code true} if this lock is currently held by any thread, or
214   *          {@code false} if not.
215   */
216  public boolean isLocked()
217  {
218    return reentrantLock.isLocked();
219  }
220
221
222
223  /**
224   * Indicates whether this lock is currently held by the current thread.
225   *
226   * @return  {@code true} if this lock is currently held by the current thread,
227   *          or {@code false} if not.
228   */
229  public boolean isHeldByCurrentThread()
230  {
231    return reentrantLock.isHeldByCurrentThread();
232  }
233
234
235
236  /**
237   * Retrieves the number of holds that the current thread has on the lock.
238   *
239   * @return  The number of holds that the current thread has on the lock.
240   */
241  public int getHoldCount()
242  {
243    return reentrantLock.getHoldCount();
244  }
245
246
247
248  /**
249   * Indicates whether any threads are currently waiting to acquire this lock.
250   *
251   * @return  {@code true} if any threads are currently waiting to acquire this
252   *          lock, or {@code false} if not.
253   */
254  public boolean hasQueuedThreads()
255  {
256    return reentrantLock.hasQueuedThreads();
257  }
258
259
260
261  /**
262   * Indicates whether the specified thread is currently waiting to acquire this
263   * lock, or {@code false} if not.
264   *
265   * @param  thread  The thread for which to make the determination.  It must
266   *                 not be {@code null}.
267   *
268   * @return  {@code true} if the specified thread is currently waiting to
269   *          acquire this lock, or {@code false} if not.
270   */
271  public boolean hasQueuedThread(final Thread thread)
272  {
273    Validator.ensureNotNull(thread);
274
275    return reentrantLock.hasQueuedThread(thread);
276  }
277
278
279
280  /**
281   * Retrieves an estimate of the number of threads currently waiting to acquire
282   * this lock.
283   *
284   * @return  An estimate of the number of threads currently waiting to acquire
285   *          this lock.
286   */
287  public int getQueueLength()
288  {
289    return reentrantLock.getQueueLength();
290  }
291
292
293
294  /**
295   * Retrieves a string representation of this lock.
296   *
297   * @return  A string representation of this lock.
298   */
299  @Override()
300  public String toString()
301  {
302    return "CloseableLock(lock=" + reentrantLock.toString() + ')';
303  }
304
305
306
307  /**
308   * This class provides a {@code Closeable} implementation that may be used to
309   * unlock a {@link CloseableLock} via Java's try-with-resources
310   * facility.
311   */
312  public final class Lock
313         implements Closeable
314  {
315    // The associated reentrant lock.
316    private final ReentrantLock lock;
317
318
319
320    /**
321     * Creates a new instance with the provided lock.
322     *
323     * @param  lock  The lock that will be unlocked when the [@link #close()}
324     *               method is called.  This must not be {@code null}.
325     */
326    private Lock(final ReentrantLock lock)
327    {
328      this.lock = lock;
329    }
330
331
332
333    /**
334     * This method does nothing.  However, calling it inside a try block when
335     * used in the try-with-resources framework can help avoid a compiler
336     * warning that the JVM will give you if you don't reference the
337     * {@code Closeable} object inside the try block.
338     */
339    public void avoidCompilerWarning()
340    {
341      // No implementation is required.
342    }
343
344
345
346    /**
347     * Unlocks the associated lock.
348     */
349    @Override()
350    public void close()
351    {
352      lock.unlock();
353    }
354  }
355}