/*
 * Copyright (C) 2011 Klaus Reimer <k@ailis.de>
 * See LICENSE.txt for licensing information.
 */

package de.ailis.usb4java.support;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import javax.usb.UsbException;
import javax.usb.UsbHostManager;
import javax.usb.UsbIrp;

import de.ailis.usb4java.Services;
import de.ailis.usb4java.topology.Usb4JavaDevice;

/**
 * Abstract base class for IRP queues.
 *
 * @author Klaus Reimer (k@ailis.de)
 * @param <T>
 *            The type of IRPs this queue holds.
 */
public abstract class AbstractIrpQueue<T extends UsbIrp>
{
    /** The queued packets. */
    private final Queue<T> irps = new ConcurrentLinkedQueue<T>();

    /** The queue processor thread. */
    private Thread processor;

    /** The USB device. */
    protected final Usb4JavaDevice device;

    /**
     * Constructor.
     *
     * @param device
     *            The USB device.
     */
    public AbstractIrpQueue(final Usb4JavaDevice device)
    {
        this.device = device;
    }

    /**
     * Queues the specified control IRP for processing.
     *
     * @param irp
     *            The control IRP to queue.
     */
    public final void add(final T irp)
    {
        this.irps.add(irp);

        // Start the queue processor if not already running.
        if (this.processor == null)
        {
            this.processor = new Thread(new Runnable()
            {
                @Override
                public void run()
                {
                    process();
                }
            });
            this.processor.setDaemon(true);
            this.processor.start();
        }
    }

    /**
     * Processes the queue. Methods returns when the queue is empty.
     */
    final void process()
    {
        while (true)
        {
            // Get the next IRP
            final T irp = this.irps.poll();

            // Process the IRP
            try
            {
                UsbLock.acquire();
                try
                {
                    processIrp(irp);
                }
                finally
                {
                    UsbLock.release();
                }
            }
            catch (final UsbException e)
            {
                irp.setUsbException(e);
            }
            irp.complete();
            finishIrp(irp);

            // When no more IRPs are present in the queue then terminate
            // the thread.
            if (this.irps.isEmpty())
            {
                this.processor = null;
                synchronized (this.irps)
                {
                    this.irps.notifyAll();
                }
                break;
            }
        }
    }

    /**
     * Processes the IRP.
     *
     * @param irp
     *            The IRP to process.
     * @throws UsbException
     *             When processing the IRP fails.
     */
    protected abstract void processIrp(final T irp) throws UsbException;

    /**
     * Called after IRP has finished. This can be implemented to send events for
     * example.
     *
     * @param irp
     *            The IRP which has been finished.
     */
    protected abstract void finishIrp(final UsbIrp irp);

    /**
     * Aborts all queued IRPs. The IRP which is currently processed can't be
     * aborted. This method returns as soon as no more IRPs are in the queue and
     * no more are processed.
     */
    public final void abort()
    {
        this.irps.clear();
        while (isBusy())
        {
            try
            {
                synchronized (this.irps)
                {
                    this.irps.wait();
                }
            }
            catch (final InterruptedException e)
            {
                Thread.currentThread().interrupt();
            }
        }
    }

    /**
     * Checks if queue is busy. A busy queue is a queue which is currently
     * processing IRPs or which still has IRPs in the queue.
     *
     * @return True if queue is busy, false if not.
     */
    public final boolean isBusy()
    {
        return !this.irps.isEmpty() || this.processor != null;
    }

    /**
     * Returns the configuration.
     *
     * @return The configuration.
     */
    protected Config getConfig()
    {
        try
        {
            return ((Services) UsbHostManager.getUsbServices()).getConfig();
        }
        catch (final UsbException e)
        {
            // Can't happen because we can't get to this point when USB
            // services are not available.
            throw new RuntimeException(e.toString(), e);
        }
    }
}

@@ -1780,43 +1780,8 @@
   %7B%0A
-        while (true)%0A        %7B%0A    
     
@@ -1812,26 +1812,16 @@
         
-    final 
 T irp = 
@@ -1834,24 +1834,61 @@
 rps.poll();%0A
+        while (irp != null)%0A        %7B
 %0A           
@@ -2299,56 +2299,8 @@
     
-    irp.complete();%0A            finishIrp(irp);%0A
 %0A   
@@ -2315,92 +2315,113 @@
  // 
-When no more
+Get next
  IRP
-s
  a
-re present in the queue then terminate%0A            // the thread.
+nd mark the thread as closing before sending the%0A            // events for the previous IRP
 %0A   
@@ -2425,28 +2425,36 @@
             
-if (
+T nextIrp = 
 this.irps.is
@@ -2455,18 +2455,15 @@
 rps.
-isEmpty())
+poll();
 %0A   
@@ -2471,33 +2471,36 @@
         
-%7B%0A               
+if (nextIrp == null)
  this.pr
@@ -2531,54 +2531,103 @@
     
-    synchronized (this.irps)%0A                %7B
+%0A            // Finish the previous IRP%0A            irp.complete();%0A            finishIrp(irp);
 %0A   
@@ -2631,32 +2631,37 @@
             
+%0A    
         
 this.irps.no
@@ -2652,29 +2652,66 @@
     
-this.irps.notifyAll()
+// Process next IRP (if present)%0A            irp = nextIrp
 ;%0A  
@@ -2712,32 +2712,34 @@
 rp;%0A        
+%7D%0A
         
 %7D%0A          
@@ -2718,33 +2718,32 @@
       %7D%0A        
-%7D
 %0A               
@@ -2739,36 +2739,182 @@
     
-        break;%0A            %7D
+// No more IRPs are present in the queue so terminate the thread.%0A        this.processor = null;%0A        synchronized (this.irps)%0A        %7B%0A            this.irps.notifyAll();
 %0A   

Improve IRP processing thread execution