<commit_before>package jp.kshoji.driver.midi.device;

import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.Queue;

import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbRequest;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.Message;

/**
 * MIDI Output Device stop() method must be called when the application will be destroyed.
 * 
 * @author K.Shoji
 */
public final class MidiOutputDevice {

	private final UsbDevice usbDevice;
	final UsbDeviceConnection usbDeviceConnection;
	private final UsbInterface usbInterface;
	final UsbEndpoint outputEndpoint;

	final WaiterThread waiterThread;

	/**
	 * constructor
	 * 
	 * @param usbDevice
	 * @param usbDeviceConnection
	 * @param usbInterface
	 * @param usbEndpoint
	 */
	public MidiOutputDevice(UsbDevice usbDevice, UsbDeviceConnection usbDeviceConnection, UsbInterface usbInterface, UsbEndpoint usbEndpoint) {
		this.usbDevice = usbDevice;
		this.usbDeviceConnection = usbDeviceConnection;
		this.usbInterface = usbInterface;

		waiterThread = new WaiterThread();

		outputEndpoint = usbEndpoint;
		if (outputEndpoint == null) {
			throw new IllegalArgumentException("Output endpoint was not found.");
		}

		this.usbDeviceConnection.claimInterface(this.usbInterface, true);

		waiterThread.start();
	}

	/**
	 * stop to use this device.
	 */
	public void stop() {
		usbDeviceConnection.releaseInterface(usbInterface);

		resume();
		waiterThread.stopFlag = true;
		waiterThread.interrupt();

		// blocks while the thread will stop
		while (waiterThread.isAlive()) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// ignore
			}
		}
	}
	
	/**
	 * Suspends event sending
	 */
	public void suspend() {
		waiterThread.suspendFlag = true;
		waiterThread.interrupt();
	}

	/**
	 * Resumes event sending
	 */
	public void resume() {
		waiterThread.suspendFlag = false;
		waiterThread.interrupt();
	}

	/**
	 * @return the usbDevice
	 */
	public UsbDevice getUsbDevice() {
		return usbDevice;
	}

	/**
	 * @return the usbInterface
	 */
	public UsbInterface getUsbInterface() {
		return usbInterface;
	}

	/**
	 * @return the usbEndpoint
	 */
	public UsbEndpoint getUsbEndpoint() {
		return outputEndpoint;
	}

	/**
	 * Sending thread for output data. Loops infinitely while stopFlag == false.
	 * 
	 * @author K.Shoji
	 */
	private final class WaiterThread extends Thread {
		final Queue<byte[]> queue = new LinkedList<byte[]>();

		volatile boolean stopFlag;
		volatile boolean suspendFlag;

		private UsbRequest usbRequest;

		/**
		 * constructor
		 * 
		 * @param handler
		 */
		WaiterThread() {
			stopFlag = false;
			suspendFlag = false;
		}

		private final Handler handler = new Handler(new Callback() {
			@Override
			public synchronized boolean handleMessage(Message msg) {
				if (!(msg.obj instanceof byte[])) {
					return false;
				}

				byte[] writeBuffer = (byte[]) msg.obj;

				synchronized (queue) {
					queue.add(writeBuffer);
				}

				// message has been queued, so interrupt the waiter thread
				waiterThread.interrupt();

				// message handled successfully
				return true;
			}
		});

		Handler getHandler() {
			return handler;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Thread#run()
		 */
		@Override
		public void run() {
			byte[] dequedDataBuffer;
			int queueSize = 0;
			final int maxPacketSize = outputEndpoint.getMaxPacketSize();
			byte[] endpointBuffer = new byte[maxPacketSize];
			int endpointBufferLength = 0;
			int bufferPosition;
			int dequedDataBufferLength;
			
			while (stopFlag == false) {
				if (suspendFlag) {
					try {
						// sleep until interrupted
						sleep(500);
					} catch (InterruptedException e) {
						// interrupted: event queued, or stopFlag/suspendFlag changed.
					}
					continue;
				}
				
				dequedDataBuffer = null;
				synchronized (queue) {
					queueSize = queue.size();
					if (queueSize > 0) {
						dequedDataBuffer = queue.poll();
					}
				}

				if (dequedDataBuffer != null) {
					dequedDataBufferLength = dequedDataBuffer.length;
					
					// usbRequest.queue() is not thread-safe
					synchronized (usbDeviceConnection) {
						if (usbRequest == null) {
							usbRequest = new UsbRequest();
							usbRequest.initialize(usbDeviceConnection, outputEndpoint);
						}

						// usbRequest can't send data larger than maxPacketSize. split the data.
						for (bufferPosition = 0; bufferPosition < dequedDataBufferLength; bufferPosition += maxPacketSize) {
							if (bufferPosition + maxPacketSize > dequedDataBufferLength) {
								endpointBufferLength = dequedDataBufferLength % maxPacketSize;
							} else {
								endpointBufferLength = maxPacketSize;
							}
							System.arraycopy(dequedDataBuffer, bufferPosition, endpointBuffer, 0, endpointBufferLength);
							<commit_msg>Ignore events while MidiOutputDevice thread is suspended.<commit_after>package jp.kshoji.driver.midi.device;

import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.Queue;

import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbRequest;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.Message;

/**
 * MIDI Output Device stop() method must be called when the application will be destroyed.
 * 
 * @author K.Shoji
 */
public final class MidiOutputDevice {

	private final UsbDevice usbDevice;
	final UsbDeviceConnection usbDeviceConnection;
	private final UsbInterface usbInterface;
	final UsbEndpoint outputEndpoint;

	final WaiterThread waiterThread;

	/**
	 * constructor
	 * 
	 * @param usbDevice
	 * @param usbDeviceConnection
	 * @param usbInterface
	 * @param usbEndpoint
	 */
	public MidiOutputDevice(UsbDevice usbDevice, UsbDeviceConnection usbDeviceConnection, UsbInterface usbInterface, UsbEndpoint usbEndpoint) {
		this.usbDevice = usbDevice;
		this.usbDeviceConnection = usbDeviceConnection;
		this.usbInterface = usbInterface;

		waiterThread = new WaiterThread();

		outputEndpoint = usbEndpoint;
		if (outputEndpoint == null) {
			throw new IllegalArgumentException("Output endpoint was not found.");
		}

		this.usbDeviceConnection.claimInterface(this.usbInterface, true);

		waiterThread.start();
	}

	/**
	 * stop to use this device.
	 */
	public void stop() {
		usbDeviceConnection.releaseInterface(usbInterface);

		resume();
		waiterThread.stopFlag = true;
		waiterThread.interrupt();

		// blocks while the thread will stop
		while (waiterThread.isAlive()) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// ignore
			}
		}
	}
	
	/**
	 * Suspends event sending
	 */
	public void suspend() {
		waiterThread.suspendFlag = true;
		waiterThread.interrupt();
	}

	/**
	 * Resumes event sending
	 */
	public void resume() {
		waiterThread.suspendFlag = false;
		waiterThread.interrupt();
	}

	/**
	 * @return the usbDevice
	 */
	public UsbDevice getUsbDevice() {
		return usbDevice;
	}

	/**
	 * @return the usbInterface
	 */
	public UsbInterface getUsbInterface() {
		return usbInterface;
	}

	/**
	 * @return the usbEndpoint
	 */
	public UsbEndpoint getUsbEndpoint() {
		return outputEndpoint;
	}

	/**
	 * Sending thread for output data. Loops infinitely while stopFlag == false.
	 * 
	 * @author K.Shoji
	 */
	private final class WaiterThread extends Thread {
		final Queue<byte[]> queue = new LinkedList<byte[]>();

		volatile boolean stopFlag;
		volatile boolean suspendFlag;

		private UsbRequest usbRequest;

		/**
		 * constructor
		 * 
		 * @param handler
		 */
		WaiterThread() {
			stopFlag = false;
			suspendFlag = false;
		}

		private final Handler handler = new Handler(new Callback() {
			@Override
			public synchronized boolean handleMessage(Message msg) {
				if (!(msg.obj instanceof byte[])) {
					return false;
				}

				byte[] writeBuffer = (byte[]) msg.obj;

				synchronized (queue) {
					queue.add(writeBuffer);
				}

				// message has been queued, so interrupt the waiter thread
				waiterThread.interrupt();

				// message handled successfully
				return true;
			}
		});

		Handler getHandler() {
			return handler;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Thread#run()
		 */
		@Override
		public void run() {
			byte[] dequedDataBuffer;
			int queueSize = 0;
			final int maxPacketSize = outputEndpoint.getMaxPacketSize();
			byte[] endpointBuffer = new byte[maxPacketSize];
			int endpointBufferLength = 0;
			int bufferPosition;
			int dequedDataBufferLength;
			
			while (stopFlag == false) {
				dequedDataBuffer = null;
				synchronized (queue) {
					queueSize = queue.size();
					if (queueSize > 0) {
						dequedDataBuffer = queue.poll();
					}
				}

				if (suspendFlag) {
					try {
						// sleep until interrupted
						sleep(500);
					} catch (InterruptedException e) {
						// interrupted: event queued, or stopFlag/suspendFlag changed.
					}
					continue;
				}
				
				if (dequedDataBuffer != null) {
					dequedDataBufferLength = dequedDataBuffer.length;
					
					// usbRequest.queue() is not thread-safe
					synchronized (usbDeviceConnection) {
						if (usbRequest == null) {
							usbRequest = new UsbRequest();
							usbRequest.initialize(usbDeviceConnection, outputEndpoint);
						}

						// usbRequest can't send data larger than maxPacketSize. split the data.
						for (bufferPosition = 0; bufferPosition < dequedDataBufferLength; bufferPosition += maxPacketSize) {
							if (bufferPosition + maxPacketSize > dequedDataBufferLength) {
								endpointBufferLength = dequedDataBufferLength % maxPacketSize;
							} else {
								endpointBufferLength = maxPacketSize;
							}
							System.arraycopy(dequedDataBuffer, bufferPosition, endpointBuffer, 0, endpointBufferLength);
							
{'fraction_non_alphanumeric': 0.0999602938256899, 'fraction_numerical': 0.002183839587055787, 'mean_word_length': 3.357698961937716, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6614327', 'n_tokens_mistral': 3632, 'n_tokens_neox': 2908, 'n_words': 742}