michael@0: /*
michael@0: * ====================================================================
michael@0: *
michael@0: * Licensed to the Apache Software Foundation (ASF) under one or more
michael@0: * contributor license agreements. See the NOTICE file distributed with
michael@0: * this work for additional information regarding copyright ownership.
michael@0: * The ASF licenses this file to You under the Apache License, Version 2.0
michael@0: * (the "License"); you may not use this file except in compliance with
michael@0: * the License. You may obtain a copy of the License at
michael@0: *
michael@0: * http://www.apache.org/licenses/LICENSE-2.0
michael@0: *
michael@0: * Unless required by applicable law or agreed to in writing, software
michael@0: * distributed under the License is distributed on an "AS IS" BASIS,
michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
michael@0: * See the License for the specific language governing permissions and
michael@0: * limitations under the License.
michael@0: * ====================================================================
michael@0: *
michael@0: * This software consists of voluntary contributions made by many
michael@0: * individuals on behalf of the Apache Software Foundation. For more
michael@0: * information on the Apache Software Foundation, please see
michael@0: * .
michael@0: *
michael@0: */
michael@0:
michael@0: package ch.boye.httpclientandroidlib.impl.conn.tsccm;
michael@0:
michael@0: import java.io.IOException;
michael@0: import java.util.ListIterator;
michael@0: import java.util.Queue;
michael@0: import java.util.LinkedList;
michael@0:
michael@0: import ch.boye.httpclientandroidlib.annotation.NotThreadSafe;
michael@0:
michael@0: import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog;
michael@0: /* LogFactory removed by HttpClient for Android script. */
michael@0: import ch.boye.httpclientandroidlib.conn.OperatedClientConnection;
michael@0: import ch.boye.httpclientandroidlib.conn.params.ConnPerRoute;
michael@0: import ch.boye.httpclientandroidlib.conn.routing.HttpRoute;
michael@0: import ch.boye.httpclientandroidlib.util.LangUtils;
michael@0:
michael@0:
michael@0: /**
michael@0: * A connection sub-pool for a specific route, used by {@link ConnPoolByRoute}.
michael@0: * The methods in this class are unsynchronized. It is expected that the
michael@0: * containing pool takes care of synchronization.
michael@0: *
michael@0: * @since 4.0
michael@0: */
michael@0: @NotThreadSafe // e.g. numEntries, freeEntries,
michael@0: public class RouteSpecificPool {
michael@0:
michael@0: public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass());
michael@0:
michael@0: /** The route this pool is for. */
michael@0: protected final HttpRoute route; //Immutable
michael@0:
michael@0: @Deprecated
michael@0: protected final int maxEntries;
michael@0:
michael@0: /** Connections per route */
michael@0: protected final ConnPerRoute connPerRoute;
michael@0:
michael@0: /**
michael@0: * The list of free entries.
michael@0: * This list is managed LIFO, to increase idle times and
michael@0: * allow for closing connections that are not really needed.
michael@0: */
michael@0: protected final LinkedList freeEntries;
michael@0:
michael@0: /** The list of threads waiting for this pool. */
michael@0: protected final Queue waitingThreads;
michael@0:
michael@0: /** The number of created entries. */
michael@0: protected int numEntries;
michael@0:
michael@0:
michael@0: /**
michael@0: * @deprecated use {@link RouteSpecificPool#RouteSpecificPool(HttpRoute, ConnPerRoute)}
michael@0: */
michael@0: @Deprecated
michael@0: public RouteSpecificPool(HttpRoute route, int maxEntries) {
michael@0: this.route = route;
michael@0: this.maxEntries = maxEntries;
michael@0: this.connPerRoute = new ConnPerRoute() {
michael@0: public int getMaxForRoute(HttpRoute route) {
michael@0: return RouteSpecificPool.this.maxEntries;
michael@0: }
michael@0: };
michael@0: this.freeEntries = new LinkedList();
michael@0: this.waitingThreads = new LinkedList();
michael@0: this.numEntries = 0;
michael@0: }
michael@0:
michael@0:
michael@0: /**
michael@0: * Creates a new route-specific pool.
michael@0: *
michael@0: * @param route the route for which to pool
michael@0: * @param connPerRoute the connections per route configuration
michael@0: */
michael@0: public RouteSpecificPool(HttpRoute route, ConnPerRoute connPerRoute) {
michael@0: this.route = route;
michael@0: this.connPerRoute = connPerRoute;
michael@0: this.maxEntries = connPerRoute.getMaxForRoute(route);
michael@0: this.freeEntries = new LinkedList();
michael@0: this.waitingThreads = new LinkedList();
michael@0: this.numEntries = 0;
michael@0: }
michael@0:
michael@0:
michael@0: /**
michael@0: * Obtains the route for which this pool is specific.
michael@0: *
michael@0: * @return the route
michael@0: */
michael@0: public final HttpRoute getRoute() {
michael@0: return route;
michael@0: }
michael@0:
michael@0:
michael@0: /**
michael@0: * Obtains the maximum number of entries allowed for this pool.
michael@0: *
michael@0: * @return the max entry number
michael@0: */
michael@0: public final int getMaxEntries() {
michael@0: return maxEntries;
michael@0: }
michael@0:
michael@0:
michael@0: /**
michael@0: * Indicates whether this pool is unused.
michael@0: * A pool is unused if there is neither an entry nor a waiting thread.
michael@0: * All entries count, not only the free but also the allocated ones.
michael@0: *
michael@0: * @return true
if this pool is unused,
michael@0: * false
otherwise
michael@0: */
michael@0: public boolean isUnused() {
michael@0: return (numEntries < 1) && waitingThreads.isEmpty();
michael@0: }
michael@0:
michael@0:
michael@0: /**
michael@0: * Return remaining capacity of this pool
michael@0: *
michael@0: * @return capacity
michael@0: */
michael@0: public int getCapacity() {
michael@0: return connPerRoute.getMaxForRoute(route) - numEntries;
michael@0: }
michael@0:
michael@0:
michael@0: /**
michael@0: * Obtains the number of entries.
michael@0: * This includes not only the free entries, but also those that
michael@0: * have been created and are currently issued to an application.
michael@0: *
michael@0: * @return the number of entries for the route of this pool
michael@0: */
michael@0: public final int getEntryCount() {
michael@0: return numEntries;
michael@0: }
michael@0:
michael@0:
michael@0: /**
michael@0: * Obtains a free entry from this pool, if one is available.
michael@0: *
michael@0: * @return an available pool entry, or null
if there is none
michael@0: */
michael@0: public BasicPoolEntry allocEntry(final Object state) {
michael@0: if (!freeEntries.isEmpty()) {
michael@0: ListIterator it = freeEntries.listIterator(freeEntries.size());
michael@0: while (it.hasPrevious()) {
michael@0: BasicPoolEntry entry = it.previous();
michael@0: if (entry.getState() == null || LangUtils.equals(state, entry.getState())) {
michael@0: it.remove();
michael@0: return entry;
michael@0: }
michael@0: }
michael@0: }
michael@0: if (getCapacity() == 0 && !freeEntries.isEmpty()) {
michael@0: BasicPoolEntry entry = freeEntries.remove();
michael@0: entry.shutdownEntry();
michael@0: OperatedClientConnection conn = entry.getConnection();
michael@0: try {
michael@0: conn.close();
michael@0: } catch (IOException ex) {
michael@0: log.debug("I/O error closing connection", ex);
michael@0: }
michael@0: return entry;
michael@0: }
michael@0: return null;
michael@0: }
michael@0:
michael@0:
michael@0: /**
michael@0: * Returns an allocated entry to this pool.
michael@0: *
michael@0: * @param entry the entry obtained from {@link #allocEntry allocEntry}
michael@0: * or presented to {@link #createdEntry createdEntry}
michael@0: */
michael@0: public void freeEntry(BasicPoolEntry entry) {
michael@0:
michael@0: if (numEntries < 1) {
michael@0: throw new IllegalStateException
michael@0: ("No entry created for this pool. " + route);
michael@0: }
michael@0: if (numEntries <= freeEntries.size()) {
michael@0: throw new IllegalStateException
michael@0: ("No entry allocated from this pool. " + route);
michael@0: }
michael@0: freeEntries.add(entry);
michael@0: }
michael@0:
michael@0:
michael@0: /**
michael@0: * Indicates creation of an entry for this pool.
michael@0: * The entry will not be added to the list of free entries,
michael@0: * it is only recognized as belonging to this pool now. It can then
michael@0: * be passed to {@link #freeEntry freeEntry}.
michael@0: *
michael@0: * @param entry the entry that was created for this pool
michael@0: */
michael@0: public void createdEntry(BasicPoolEntry entry) {
michael@0:
michael@0: if (!route.equals(entry.getPlannedRoute())) {
michael@0: throw new IllegalArgumentException
michael@0: ("Entry not planned for this pool." +
michael@0: "\npool: " + route +
michael@0: "\nplan: " + entry.getPlannedRoute());
michael@0: }
michael@0:
michael@0: numEntries++;
michael@0: }
michael@0:
michael@0:
michael@0: /**
michael@0: * Deletes an entry from this pool.
michael@0: * Only entries that are currently free in this pool can be deleted.
michael@0: * Allocated entries can not be deleted.
michael@0: *
michael@0: * @param entry the entry to delete from this pool
michael@0: *
michael@0: * @return true
if the entry was found and deleted, or
michael@0: * false
if the entry was not found
michael@0: */
michael@0: public boolean deleteEntry(BasicPoolEntry entry) {
michael@0:
michael@0: final boolean found = freeEntries.remove(entry);
michael@0: if (found)
michael@0: numEntries--;
michael@0: return found;
michael@0: }
michael@0:
michael@0:
michael@0: /**
michael@0: * Forgets about an entry from this pool.
michael@0: * This method is used to indicate that an entry
michael@0: * {@link #allocEntry allocated}
michael@0: * from this pool has been lost and will not be returned.
michael@0: */
michael@0: public void dropEntry() {
michael@0: if (numEntries < 1) {
michael@0: throw new IllegalStateException
michael@0: ("There is no entry that could be dropped.");
michael@0: }
michael@0: numEntries--;
michael@0: }
michael@0:
michael@0:
michael@0: /**
michael@0: * Adds a waiting thread.
michael@0: * This pool makes no attempt to match waiting threads with pool entries.
michael@0: * It is the caller's responsibility to check that there is no entry
michael@0: * before adding a waiting thread.
michael@0: *
michael@0: * @param wt the waiting thread
michael@0: */
michael@0: public void queueThread(WaitingThread wt) {
michael@0: if (wt == null) {
michael@0: throw new IllegalArgumentException
michael@0: ("Waiting thread must not be null.");
michael@0: }
michael@0: this.waitingThreads.add(wt);
michael@0: }
michael@0:
michael@0:
michael@0: /**
michael@0: * Checks whether there is a waiting thread in this pool.
michael@0: *
michael@0: * @return true
if there is a waiting thread,
michael@0: * false
otherwise
michael@0: */
michael@0: public boolean hasThread() {
michael@0: return !this.waitingThreads.isEmpty();
michael@0: }
michael@0:
michael@0:
michael@0: /**
michael@0: * Returns the next thread in the queue.
michael@0: *
michael@0: * @return a waiting thread, or null
if there is none
michael@0: */
michael@0: public WaitingThread nextThread() {
michael@0: return this.waitingThreads.peek();
michael@0: }
michael@0:
michael@0:
michael@0: /**
michael@0: * Removes a waiting thread, if it is queued.
michael@0: *
michael@0: * @param wt the waiting thread
michael@0: */
michael@0: public void removeThread(WaitingThread wt) {
michael@0: if (wt == null)
michael@0: return;
michael@0:
michael@0: this.waitingThreads.remove(wt);
michael@0: }
michael@0:
michael@0:
michael@0: } // class RouteSpecificPool