1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.camera.one.v2.sharedimagereader.ticketpool;
18
19import com.android.camera.async.ConcurrentState;
20import com.android.camera.async.Observable;
21import com.android.camera.async.SafeCloseable;
22
23import java.util.ArrayList;
24import java.util.Collection;
25import java.util.LinkedList;
26import java.util.List;
27import java.util.concurrent.atomic.AtomicBoolean;
28import java.util.concurrent.locks.Condition;
29import java.util.concurrent.locks.ReentrantLock;
30
31import javax.annotation.Nonnull;
32import javax.annotation.concurrent.GuardedBy;
33
34/**
35 * A ticket pool with a finite number of tickets.
36 */
37public final class FiniteTicketPool implements TicketPool, SafeCloseable {
38    private class TicketImpl implements Ticket {
39        private final AtomicBoolean mClosed;
40
41        public TicketImpl() {
42            mClosed = new AtomicBoolean(false);
43        }
44
45        @Override
46        public void close() {
47            boolean alreadyClosed = mClosed.getAndSet(true);
48            if (!alreadyClosed) {
49                synchronized (mLock) {
50                    releaseTicket();
51                    updateAvailableTicketCount();
52                }
53            }
54        }
55    }
56
57    private class Waiter {
58        private final int mTicketsRequested;
59        private final Condition mCondition;
60
61        private Waiter(int ticketsRequested, Condition condition) {
62            mTicketsRequested = ticketsRequested;
63            mCondition = condition;
64        }
65
66        public Condition getCondition() {
67            return mCondition;
68        }
69
70        public int getTicketsRequested() {
71            return mTicketsRequested;
72        }
73    }
74
75    private final int mMaxCapacity;
76    private final ReentrantLock mLock;
77    @GuardedBy("mLock")
78    private final LinkedList<Waiter> mWaiters;
79    private final ConcurrentState<Integer> mAvailableTicketCount;
80    @GuardedBy("mLock")
81    private int mTickets;
82    @GuardedBy("mLock")
83    private boolean mClosed;
84
85    public FiniteTicketPool(int capacity) {
86        mMaxCapacity = capacity;
87        mLock = new ReentrantLock(true);
88        mTickets = capacity;
89        mWaiters = new LinkedList<>();
90        mClosed = false;
91        mAvailableTicketCount = new ConcurrentState<>(capacity);
92    }
93
94    @GuardedBy("mLock")
95    private void releaseTicket() {
96        mLock.lock();
97        try {
98            mTickets++;
99
100            // Wake up waiters in order, so long as their requested number of
101            // tickets can be satisfied.
102            int ticketsRemaining = mTickets;
103            Waiter nextWaiter = mWaiters.peekFirst();
104            while (nextWaiter != null && nextWaiter.getTicketsRequested() <= ticketsRemaining) {
105                ticketsRemaining -= nextWaiter.getTicketsRequested();
106                nextWaiter.getCondition().signal();
107
108                mWaiters.removeFirst();
109                nextWaiter = mWaiters.peekFirst();
110            }
111        } finally {
112            mLock.unlock();
113        }
114    }
115
116    @Nonnull
117    @Override
118    public Collection<Ticket> acquire(int tickets) throws InterruptedException,
119            NoCapacityAvailableException {
120        mLock.lock();
121        try {
122            if (tickets > mMaxCapacity || tickets < 0) {
123                throw new NoCapacityAvailableException();
124            }
125            Waiter thisWaiter = new Waiter(tickets, mLock.newCondition());
126            mWaiters.addLast(thisWaiter);
127            updateAvailableTicketCount();
128            try {
129                while (mTickets < tickets && !mClosed) {
130                    thisWaiter.getCondition().await();
131                }
132                if (mClosed) {
133                    throw new NoCapacityAvailableException();
134                }
135
136                mTickets -= tickets;
137
138                updateAvailableTicketCount();
139
140                List<Ticket> ticketList = new ArrayList<>();
141                for (int i = 0; i < tickets; i++) {
142                    ticketList.add(new TicketImpl());
143                }
144                return ticketList;
145            } finally {
146                mWaiters.remove(thisWaiter);
147                updateAvailableTicketCount();
148            }
149        } finally {
150            mLock.unlock();
151        }
152    }
153
154    @GuardedBy("mLock")
155    private void updateAvailableTicketCount() {
156        if (mClosed || !mWaiters.isEmpty()) {
157            mAvailableTicketCount.update(0);
158        } else {
159            mAvailableTicketCount.update(mTickets);
160        }
161    }
162
163    @Nonnull
164    @Override
165    public Observable<Integer> getAvailableTicketCount() {
166        return mAvailableTicketCount;
167    }
168
169    @Override
170    public Ticket tryAcquire() {
171        mLock.lock();
172        try {
173            if (!mClosed && mWaiters.isEmpty() && mTickets >= 1) {
174                mTickets--;
175                updateAvailableTicketCount();
176                return new TicketImpl();
177            } else {
178                return null;
179            }
180        } finally {
181            mLock.unlock();
182        }
183    }
184
185    @Override
186    public void close() {
187        mLock.lock();
188        try {
189            if (mClosed) {
190                return;
191            }
192
193            mClosed = true;
194
195            for (Waiter waiter : mWaiters) {
196                waiter.getCondition().signal();
197            }
198
199            updateAvailableTicketCount();
200        } finally {
201            mLock.unlock();
202        }
203    }
204}
205