/* * Copyright (C) 2011 Klaus Reimer <k@ailis.de> * See LICENSE.md for licensing information. */ package org.usb4java.javax; import java.nio.ByteBuffer; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import javax.usb.UsbControlIrp; import javax.usb.UsbException; import javax.usb.UsbIrp; import javax.usb.UsbShortPacketException; import org.usb4java.DeviceHandle; import org.usb4java.LibUsb; /** * Abstract base class for IRP queues. * * @author Klaus Reimer (k@ailis.de) * @param <T> * The type of IRPs this queue holds. */ abstract class AbstractIrpQueue<T extends UsbIrp> { /** The queued packets. */ private final Queue<T> irps = new ConcurrentLinkedQueue<T>(); /** The queue processor thread. */ private volatile Thread processor; /** If queue is currently aborting. */ private volatile boolean aborting; /** The USB device. */ private final AbstractDevice device; /** * Constructor. * * @param device * The USB device. Must not be null. */ AbstractIrpQueue(final AbstractDevice device) { if (device == null) throw new IllegalArgumentException("device must be set"); 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.setName("usb4java IRP Queue Processor"); this.processor.start(); } } /** * Processes the queue. Methods returns when the queue is empty. */ final void process() { try { // Get the next IRP T irp = this.irps.poll(); // If there are no IRPs to process then mark the thread as closing // right away. Otherwise process the IRP (and more IRPs from the // queue // if present). if (irp == null) { this.processor = null; } else { while (irp != null) { // Process the IRP try { processIrp(irp); } catch (final UsbException e) { irp.setUsbException(e); } // Get next IRP and mark the thread as closing before // sending // the events for the previous IRP final T nextIrp = this.irps.poll(); if (nextIrp == null) this.processor = null; // Finish the previous IRP irp.complete(); finishIrp(irp); // Process next IRP (if present) irp = nextIrp; } } } catch (Exception e) { e.printStackTrace(); } finally { // No more IRPs are present in the queue so terminate the thread. synchronized (this.irps) { //System.out.println("done processing terminating thread"); this.irps.notifyAll(); } } } /** * 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.aborting = true; this.irps.clear(); while (isBusy()) { try { synchronized (this.irps) { if (isBusy()) this.irps.wait(); } } catch (final InterruptedException e) { Thread.currentThread().interrupt(); } } this.aborting = false; } /** * 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 final Config getConfig() { return Services.getInstance().getConfig(); } /** * Returns the USB device. * * @return The USB device. Never null. */ protected final AbstractDevice getDevice() { return this.device; } /** * Processes the control IRP. * * @param irp * The IRP to process. * @throws UsbException * When processing the IRP fails. */ protected final void processControlIrp(final UsbControlIrp irp) throws UsbException { final ByteBuffer buffer = ByteBuffer.allocateDirect(irp.getLength()); buffer.put(irp.getData(), irp.getOffset(), irp.getLength()); buffer.rewind(); final DeviceHandle handle = getDevice().open(); final int result = LibUsb.controlTransfer(handle, irp.bmRequestType(), irp.bRequest(), irp.wValue(), irp.wIndex(), buffer, getConfig() .getTimeout()); if (result < 0) { throw ExceptionUtils.createPlatformException( "Unable to submit control message", result); } buffer.rewind(); buffer.get(irp.getData(), irp.getOffset(), result); irp.setActualLength(result); if (irp.getActualLength() != irp.getLength() && !irp.getAcceptShortPacket()) { throw new UsbShortPacketException(); } } /** * Checks if this queue is currently aborting. * * @return True if queue is aborting, false if not. */ protected final boolean isAborting() { return this.aborting; } }