001package org.eclipse.aether.named.support;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  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,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.util.ArrayDeque;
023import java.util.Deque;
024import java.util.concurrent.TimeUnit;
025
026/**
027 * Named lock support implementation that is using "adapted" semaphore (to be able to use semaphores not sharing common
028 * API).
029 */
030public class AdaptedSemaphoreNamedLock
031    extends NamedLockSupport
032{
033    /**
034     * Wrapper for semaphore-like stuff, that do not share common ancestor. Semaphore must be created to support {@link
035     * Integer#MAX_VALUE} permissions.
036     */
037    public interface AdaptedSemaphore
038    {
039        boolean tryAcquire( int perms, long time, TimeUnit unit ) throws InterruptedException;
040
041        void release( int perms );
042    }
043
044    /**
045     * Count of permissions involved with "nop" locking. When required lock step is preceded with a step that already
046     * fulfills currently requested locking, no locking is needed. In other words, caller already possesses the access
047     * to lock protected resource. The "nop" locking is used to track proper "boxing" of lock/unlock calls.
048     */
049    private static final int NONE = 0;
050
051    /**
052     * Count of permissions involved with shared locking
053     */
054    private static final int SHARED = 1;
055
056    /**
057     * Count of permissions involved with exclusive locking
058     */
059    private static final int EXCLUSIVE = Integer.MAX_VALUE;
060
061    private final ThreadLocal<Deque<Integer>> threadPerms;
062
063    private final AdaptedSemaphore semaphore;
064
065    public AdaptedSemaphoreNamedLock( final String name,
066                                      final NamedLockFactorySupport factory,
067                                      final AdaptedSemaphore semaphore )
068    {
069        super( name, factory );
070        this.threadPerms = ThreadLocal.withInitial( ArrayDeque::new );
071        this.semaphore = semaphore;
072    }
073
074    @Override
075    public boolean lockShared( final long time, final TimeUnit unit ) throws InterruptedException
076    {
077        Deque<Integer> perms = threadPerms.get();
078        if ( !perms.isEmpty() )
079        { // we already own shared or exclusive lock
080            perms.push( NONE );
081            return true;
082        }
083        if ( semaphore.tryAcquire( SHARED, time, unit ) )
084        {
085            perms.push( SHARED );
086            return true;
087        }
088        return false;
089    }
090
091    @Override
092    public boolean lockExclusively( final long time, final TimeUnit unit ) throws InterruptedException
093    {
094        Deque<Integer> perms = threadPerms.get();
095        if ( !perms.isEmpty() )
096        { // we already own shared or exclusive lock
097            if ( perms.contains( EXCLUSIVE ) )
098            {
099                perms.push( NONE );
100                return true;
101            }
102            else
103            {
104                return false; // Lock upgrade not supported
105            }
106        }
107        if ( semaphore.tryAcquire( EXCLUSIVE, time, unit ) )
108        {
109            perms.push( EXCLUSIVE );
110            return true;
111        }
112        return false;
113    }
114
115    @Override
116    public void unlock()
117    {
118        Deque<Integer> steps = threadPerms.get();
119        if ( steps.isEmpty() )
120        {
121            throw new IllegalStateException( "Wrong API usage: unlock w/o lock" );
122        }
123        int step = steps.pop();
124        if ( step > NONE )
125        {
126            semaphore.release( step );
127        }
128    }
129}