001/**
002 *
003 * Copyright 2003-2007 Jive Software.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.jivesoftware.smack;
019
020import java.util.concurrent.ArrayBlockingQueue;
021import java.util.concurrent.TimeUnit;
022
023import org.jivesoftware.smack.SmackException.NoResponseException;
024import org.jivesoftware.smack.XMPPException.XMPPErrorException;
025import org.jivesoftware.smack.filter.PacketFilter;
026import org.jivesoftware.smack.packet.Packet;
027import org.jivesoftware.smack.packet.XMPPError;
028
029/**
030 * Provides a mechanism to collect packets into a result queue that pass a
031 * specified filter. The collector lets you perform blocking and polling
032 * operations on the result queue. So, a PacketCollector is more suitable to
033 * use than a {@link PacketListener} when you need to wait for a specific
034 * result.<p>
035 *
036 * Each packet collector will queue up a configured number of packets for processing before
037 * older packets are automatically dropped.  The default number is retrieved by 
038 * {@link SmackConfiguration#getPacketCollectorSize()}.
039 *
040 * @see XMPPConnection#createPacketCollector(PacketFilter)
041 * @author Matt Tucker
042 */
043public class PacketCollector {
044
045    private PacketFilter packetFilter;
046    private ArrayBlockingQueue<Packet> resultQueue;
047    private XMPPConnection connection;
048    private boolean cancelled = false;
049
050    /**
051     * Creates a new packet collector. If the packet filter is <tt>null</tt>, then
052     * all packets will match this collector.
053     *
054     * @param connection the connection the collector is tied to.
055     * @param packetFilter determines which packets will be returned by this collector.
056     */
057    protected PacketCollector(XMPPConnection connection, PacketFilter packetFilter) {
058        this(connection, packetFilter, SmackConfiguration.getPacketCollectorSize());
059    }
060
061    /**
062     * Creates a new packet collector. If the packet filter is <tt>null</tt>, then
063     * all packets will match this collector.
064     *
065     * @param connection the connection the collector is tied to.
066     * @param packetFilter determines which packets will be returned by this collector.
067     * @param maxSize the maximum number of packets that will be stored in the collector.
068     */
069    protected PacketCollector(XMPPConnection connection, PacketFilter packetFilter, int maxSize) {
070        this.connection = connection;
071        this.packetFilter = packetFilter;
072        this.resultQueue = new ArrayBlockingQueue<Packet>(maxSize);
073    }
074
075    /**
076     * Explicitly cancels the packet collector so that no more results are
077     * queued up. Once a packet collector has been cancelled, it cannot be
078     * re-enabled. Instead, a new packet collector must be created.
079     */
080    public void cancel() {
081        // If the packet collector has already been cancelled, do nothing.
082        if (!cancelled) {
083            cancelled = true;
084            connection.removePacketCollector(this);
085        }
086    }
087
088    /**
089     * Returns the packet filter associated with this packet collector. The packet
090     * filter is used to determine what packets are queued as results.
091     *
092     * @return the packet filter.
093     */
094    public PacketFilter getPacketFilter() {
095        return packetFilter;
096    }
097
098    /**
099     * Polls to see if a packet is currently available and returns it, or
100     * immediately returns <tt>null</tt> if no packets are currently in the
101     * result queue.
102     *
103     * @return the next packet result, or <tt>null</tt> if there are no more
104     *      results.
105     */
106    public Packet pollResult() {
107        return resultQueue.poll();
108    }
109
110    /**
111     * Returns the next available packet. The method call will block (not return) until a packet is
112     * available.
113     * 
114     * @return the next available packet.
115     */
116    public Packet nextResultBlockForever() {
117        try {
118            return resultQueue.take();
119        }
120        catch (InterruptedException e) {
121            throw new RuntimeException(e);
122        }
123    }
124
125    /**
126     * Returns the next available packet. The method call will block until the connection's default
127     * timeout has elapsed.
128     * 
129     * @return the next availabe packet.
130     */
131    public Packet nextResult() {
132        return nextResult(connection.getPacketReplyTimeout());
133    }
134
135    /**
136     * Returns the next available packet. The method call will block (not return)
137     * until a packet is available or the <tt>timeout</tt> has elapsed. If the
138     * timeout elapses without a result, <tt>null</tt> will be returned.
139     *
140     * @return the next available packet.
141     */
142    public Packet nextResult(long timeout) {
143        try {
144                        return resultQueue.poll(timeout, TimeUnit.MILLISECONDS);
145                }
146                catch (InterruptedException e) {
147                        throw new RuntimeException(e);
148                }
149    }
150
151    /**
152     * Returns the next available packet. The method call will block until a packet is available or
153     * the connections reply timeout has elapsed. If the timeout elapses without a result,
154     * <tt>null</tt> will be returned. This method does also cancel the PacketCollector.
155     * 
156     * @return the next available packet.
157     * @throws XMPPErrorException in case an error response.
158     * @throws NoResponseException if there was no response from the server.
159     */
160    public Packet nextResultOrThrow() throws NoResponseException, XMPPErrorException {
161        return nextResultOrThrow(connection.getPacketReplyTimeout());
162    }
163
164    /**
165     * Returns the next available packet. The method call will block until a packet is available or
166     * the <tt>timeout</tt> has elapsed. This method does also cancel the PacketCollector.
167     * 
168     * @param timeout the amount of time to wait for the next packet (in milleseconds).
169     * @return the next available packet.
170     * @throws NoResponseException if there was no response from the server.
171     * @throws XMPPErrorException in case an error response.
172     */
173    public Packet nextResultOrThrow(long timeout) throws NoResponseException, XMPPErrorException {
174        Packet result = nextResult(timeout);
175        cancel();
176        if (result == null) {
177            throw new NoResponseException();
178        }
179
180        XMPPError xmppError = result.getError();
181        if (xmppError != null) {
182            throw new XMPPErrorException(xmppError);
183        }
184
185        return result;
186    }
187
188    /**
189     * Processes a packet to see if it meets the criteria for this packet collector.
190     * If so, the packet is added to the result queue.
191     *
192     * @param packet the packet to process.
193     */
194    protected void processPacket(Packet packet) {
195        if (packet == null) {
196            return;
197        }
198        
199        if (packetFilter == null || packetFilter.accept(packet)) {
200                while (!resultQueue.offer(packet)) {
201                        // Since we know the queue is full, this poll should never actually block.
202                        resultQueue.poll();
203                }
204        }
205    }
206}