public class OSCBundle implements OSCPacket {

	private static final long serialVersionUID = 1L;

	private OSCTimeTag64 timestamp;
	private List<OSCPacket> packets;

	/**
	 * Create a new empty OSCBundle with a timestamp of immediately.
	 * You can add packets to the bundle with addPacket()
	 */
	public OSCBundle() {
		this(OSCTimeTag64.IMMEDIATE);
	}

	/**
	 * Create an OSCBundle with the specified timestamp.
	 * @param timestamp the time to execute the bundle
	 */
	public OSCBundle(final OSCTimeTag64 timestamp) {
		this(null, timestamp);
	}

	/**
	 * Creates an OSCBundle made up of the given packets
	 * with a timestamp of now.
	 * @param packets array of OSCPackets to initialize this object with
	 */
	public OSCBundle(final List<OSCPacket> packets) {
		this(packets, OSCTimeTag64.IMMEDIATE);
	}

	/**
	 * Create an OSCBundle, specifying the packets and timestamp.
	 * @param packets the packets that make up the bundle
	 * @param timestamp the time to execute the bundle
	 */
	public OSCBundle(final List<OSCPacket> packets, final OSCTimeTag64 timestamp) {

		if (null == packets) {
			this.packets = new LinkedList<>();
		} else {
			this.packets = new ArrayList<>(packets);
		}
		checkNonNullTimestamp(timestamp);
		this.timestamp = timestamp;
	}

	private static void checkNonNullTimestamp(final OSCTimeTag64 timestamp) {

		if (timestamp == null) {
			throw new IllegalArgumentException("Bundle time-stamp may not be null; you may want to "
					+ "use OSCTimeStamp.IMMEDIATE.");
		}
	}

	/**
	 * Returns the time the bundle will execute.
	 * @return will never be {@code null}
	 */
	public OSCTimeTag64 getTimestamp() {
		return timestamp;
	}

	// Public API
	/**
	 * Sets the time the bundle will execute.
	 * @param timestamp when the bundle should execute, can not be {@code null},
	 *   but {@code OSCTimeTag64.IMMEDIATE}
	 */
	@SuppressWarnings("WeakerAccess")
	public void setTimestamp(final OSCTimeTag64 timestamp) {

		checkNonNullTimestamp(timestamp);
		this.timestamp = timestamp;
	}

	/**
	 * Add a packet to the list of packets in this bundle.
	 * @param packet OSCMessage or OSCBundle
	 */
	public void addPacket(final OSCPacket packet) {
		packets.add(packet);
	}

	/**
	 * Get the packets contained in this bundle.
	 * @return the packets contained in this bundle.
	 */
	public List<OSCPacket> getPackets() {
		return Collections.unmodifiableList(packets);
	}
} public class OSCMessage implements OSCPacket {

	private static final long serialVersionUID = 1L;

	/**
	 * Java regular expression pattern matching a single invalid character.
	 * The invalid characters are:
	 * ' ', '#', '*', ',', '?', '[', ']', '{', '}'
	 */
	private static final Pattern ILLEGAL_ADDRESS_CHAR
			= Pattern.compile("[ #*,?\\[\\]{}]");

	private final String address;
	private final List<Object> arguments;
	private OSCMessageInfo info;

	/**
	 * Creates an OSCMessage with an address already initialized.
	 * @param address  the recipient of this OSC message
	 */
	public OSCMessage(final String address) {
		this(address, Collections.emptyList());
	}

	/**
	 * Creates an OSCMessage with an address
	 * and arguments already initialized.
	 * @param address  the recipient of this OSC message
	 * @param arguments  the data sent to the receiver
	 */
	public OSCMessage(final String address, final List<?> arguments) {
		this(address, arguments, null);
	}

	/**
	 * Creates an OSCMessage with an address
	 * and arguments already initialized.
	 * @param address  the recipient of this OSC message
	 * @param arguments  the data sent to the receiver
	 * @param info  meta-info about the message, or {@code null}, if not yet available
	 */
	public OSCMessage(final String address, final List<?> arguments, final OSCMessageInfo info) {
		this(address, arguments, info, true);
	}

	/**
	 * Creates an OSCMessage with an address
	 * and arguments already initialized,
	 * optionally skipping address verification.
	 * NOTE This only makes sense for testing purposes!
	 * Under normal circumstances one should never disable address checking.
	 * @param address  the recipient of this OSC message
	 * @param arguments  the data sent to the receiver
	 * @param info  meta-info about the message, or {@code null}, if not yet available
	 * @param checkAddress  whether to check the address for validity; CAUTION!
	 */
	OSCMessage(final String address, final List<?> arguments, final OSCMessageInfo info, final boolean checkAddress) {

		if (checkAddress) {
			checkAddress(address);
		}
		this.address = address;
		this.arguments = Collections.unmodifiableList(arguments);
		this.info = info;
	}

	/**
	 * The receiver of this message.
	 * @return the receiver of this OSC Message
	 */
	public String getAddress() {
		return address;
	}

	/**
	 * The arguments of this message.
	 * @return the arguments to this message
	 */
	public List<Object> getArguments() {
		return arguments;
	}

	/**
	 * Returns meta-info about this message.
	 * @return the meta-info, or {@code null}, if none are set yet.
	 */
	public OSCMessageInfo getInfo() {
		return info;
	}

	// Public API
	@SuppressWarnings("WeakerAccess")
	public boolean isInfoSet() {
		return (info != null);
	}

	// Public API
	/**
	 * Sets meta-info about this message.
	 * This method may only be called if the meta-info has not yet been set.
	 * @param info the meta-info for this message, may not be {@code null}
	 */
	@SuppressWarnings("WeakerAccess")
	public void setInfo(final OSCMessageInfo info) {

		if (this.info != null) {
			throw new IllegalStateException("The meta-info of a message may only be set once");
		}
		this.info = info;
	}

	/**
	 * Throws an exception if the given address is invalid.
	 * @param address to be checked for validity
	 */
	private static void checkAddress(final String address) {
		if (!isValidAddress(address)) {
			throw new IllegalArgumentException("Not a valid OSC address: " + address);
		}
	}

	// Public API
	/**
	 * Checks whether a given string is a valid OSC <i>Address Pattern</i>.
	 * @param address to be checked for validity
	 * @return true if the supplied string constitutes a valid OSC address
	 */
	@SuppressWarnings("WeakerAccess")
	public static boolean isValidAddress(final String address) {
		return (address != null)
				&& !address.isEmpty()
				&& (
					address.equals("#reply")
					|| (
						(address.charAt(0) == '/')
						&& !address.contains("//")
						&& !ILLEGAL_ADDRESS_CHAR.matcher(address).find()
					));
	}
} public class OSCMessageTest {

	@Rule
	public ExpectedException expectedException = ExpectedException.none();

	public static OSCMessage createUncheckedAddressMessage(final String address, final List<?> arguments, final OSCMessageInfo info) {
		return new OSCMessage(address, arguments, info, false);
	}

	/**
	 * Creates verbose assertion failures if the result does not conform to what is expected.
	 * @param result received from OSC
	 * @param expected what should have been received
	 * @param optBytes number of optional bytes in the expected array,
	 *   meaning the result may either be {@code expected.length}
	 *   or {@code expected.length - optBytes} in size
	 */
	private static void checkResultEqualsAnswer(byte[] result, byte[] expected, final int optBytes)
	{
		if ((result.length != expected.length) && (result.length != (expected.length - optBytes))) {
			Assert.fail(createErrorString("Result and expected answer aren't the same length, "
					+ result.length + " vs " + expected.length + '.', result, expected));
		}
		for (int i = 0; i < result.length; i++) {
			if (result[i] != expected[i]) {
				Assert.fail(createErrorString("Failed to convert correctly at position: " + i,
						result, expected));
			}
		}
	}

	/**
	 * Creates verbose assertion failures if the result does not conform to what is expected.
	 * The OSC 1.0 specification states, that the type tag string should to be present
	 * for all messages, even the ones without arguments, and thus at least the initial ','
	 * (plus 3 padding zeros) should always be present.
	 * Some outdated OSC implementations though, may omit it, which is why for now,
	 * we accept that as valid too.
	 * @param result received from OSC
	 * @param expected what should have been received
	 */
	public static void checkResultEqualsAnswerOptionalComma(byte[] result, byte[] expected) {
		checkResultEqualsAnswer(result, expected, 4);
	}

	static void checkResultEqualsAnswer(byte[] result, byte[] expected) {
		checkResultEqualsAnswer(result, expected, 0);
	}

	public static String createErrorString(
			final String description,
			final byte[] result,
			final byte[] expected)
	{
		return description
				+ "\n result   (str): \"" + new String(result) + '"'
				+ "\n expected (str): \"" + new String(expected) + '"'
				+ "\n result   (raw): \"" + convertByteArrayToJavaCode(result) + '"'
				+ "\n expected (raw): \"" + convertByteArrayToJavaCode(expected) + '"';
	}

	/**
	 * Can be used when creating new test cases.
	 * @param data to be converted to java code that (re-)creates this same byte array
	 * @return one line of code that creates the given data in Java
	 */
	private static String convertByteArrayToJavaCode(final byte[] data) {

		StringBuilder javaCode = new StringBuilder();

		javaCode.append("{ ");
		for (byte b : data) {
			javaCode.append((int) b).append(", ");
		}
		javaCode.delete(javaCode.length() - 2, javaCode.length());
		javaCode.append(" };");

		return javaCode.toString();
	}

	private BytesReceiver convertMessageToBytes(final OSCMessage message) {

		final ByteBuffer buffer = ByteBuffer.allocate(1024);
		final BytesReceiver bytesReceiver = new BufferBytesReceiver(buffer);
		final OSCSerializer stream = new OSCSerializerAndParserBuilder().buildSerializer(bytesReceiver);
		try {
			stream.write(message);
		} catch (final OSCSerializeException ex) {
			throw new RuntimeException(ex);
		}
		return bytesReceiver;
	}

	private byte[] convertMessageToByteArray(final OSCMessage message) {
		return convertMessageToBytes(message).toByteArray();
	}

	@Test
	public void testEmpty() {
		OSCMessage message = new OSCMessage("/empty");
		byte[] answer = { 47, 101, 109, 112, 116, 121, 0, 0, 44, 0, 0, 0 };
		byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswerOptionalComma(result, answer);
	}

	@Test
	public void testFillerBeforeCommaNone() {
		final OSCMessage message = new OSCMessage("/abcdef");
		// here we only have the addresses string terminator (0) before the ',' (44),
		// so the comma is 4 byte aligned
		final byte[] answer = { 47, 97, 98, 99, 100, 101, 102, 0, 44, 0, 0, 0 };
		final byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswerOptionalComma(result, answer);
	}

	@Test
	public void testFillerBeforeCommaOne() {
		final OSCMessage message = new OSCMessage("/abcde");
		// here we have one padding 0 after the addresses string terminator (also 0)
		// and before the ',' (44), so the comma is 4 byte aligned
		final byte[] answer = { 47, 97, 98, 99, 100, 101, 0, 0, 44, 0, 0, 0 };
		final byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswerOptionalComma(result, answer);
	}

	@Test
	public void testFillerBeforeCommaTwo() {
		final OSCMessage message = new OSCMessage("/abcd");
		// here we have two padding 0's after the addresses string terminator (also 0)
		// and before the ',' (44), so the comma is 4 byte aligned
		final byte[] answer = { 47, 97, 98, 99, 100, 0, 0, 0, 44, 0, 0, 0 };
		final byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswerOptionalComma(result, answer);
	}

	@Test
	public void testFillerBeforeCommaThree() {
		final OSCMessage message = new OSCMessage("/abcdefg");
		// here we have three padding 0's after the addresses string terminator (also 0)
		// and before the ',' (44), so the comma is 4 byte aligned
		final byte[] answer = { 47, 97, 98, 99, 100, 101, 102, 103, 0, 0, 0, 0, 44, 0, 0, 0 };
		final byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswerOptionalComma(result, answer);
	}

	@Test
	public void testArgumentInteger() {
		final List<Object> args = new ArrayList<>(1);
		args.add(99);
		final OSCMessage message = new OSCMessage("/int", args);
		final byte[] answer = { 47, 105, 110, 116, 0, 0, 0, 0, 44, 105, 0, 0, 0, 0, 0, 99 };
		final byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswer(result, answer);
	}

	@Test
	public void testArgumentFloat() {
		final List<Object> args = new ArrayList<>(1);
		args.add(999.9f);
		final OSCMessage message = new OSCMessage("/float", args);
		final byte[] answer
				= { 47, 102, 108, 111, 97, 116, 0, 0, 44, 102, 0, 0, 68, 121, -7, -102 };
		final byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswer(result, answer);
	}

	@Test
	public void testArgumentDouble() {
		final List<Object> args = new ArrayList<>(1);
		args.add(777777.777);
		final OSCMessage message = new OSCMessage("/double", args);
		final byte[] answer = {
			47, 100, 111, 117, 98, 108, 101, 0, 44, 100, 0, 0, 65, 39, -68, 99, -115, -46, -15, -86
		};
		final byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswer(result, answer);
	}

	@Test
	public void testArgumentCharacter() {
		final List<Object> args = new ArrayList<>(1);
		args.add('x');
		final OSCMessage message = new OSCMessage("/char", args);
		final byte[] answer = { 47, 99, 104, 97, 114, 0, 0, 0, 44, 99, 0, 0, 0, 0, 0, 120 };
		final byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswer(result, answer);
	}

	@Test
	public void testArgumentBlob() {
		final List<Object> args = new ArrayList<>(1);
		args.add(new byte[] { -1, 0, 1 });
		final OSCMessage message = new OSCMessage("/blob", args);
		final byte[] answer
				= { 47, 98, 108, 111, 98, 0, 0, 0, 44, 98, 0, 0, 0, 0, 0, 3, -1, 0, 1, 0 };
		final byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswer(result, answer);
	}

	@Test
	public void testArgumentImpulse() {
		final List<Object> args = new ArrayList<>(1);
		args.add(OSCImpulse.INSTANCE);
		final OSCMessage message = new OSCMessage("/impulse", args);
		final byte[] answer = { 47, 105, 109, 112, 117, 108, 115, 101, 0, 0, 0, 0, 44, 73, 0, 0 };
		final byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswer(result, answer);
	}

	@Test
	public void testArgumentLong() {
		final List<Object> args = new ArrayList<>(1);
		args.add(Long.MAX_VALUE);
		final OSCMessage message = new OSCMessage("/long", args);
		final byte[] answer = {
			47, 108, 111, 110, 103, 0, 0, 0, 44, 104, 0, 0, 127, -1, -1, -1, -1, -1, -1, -1 };
		final byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswer(result, answer);
	}

	private static Calendar createCalendar() {
		return Calendar.getInstance(TimeZone.getTimeZone("Z"));
	}

	@Test
	public void testArgumentTimestamp0() {
		final List<Object> args = new ArrayList<>(1);
		args.add(OSCTimeTag64.valueOf(new Date(0L)));
		final OSCMessage message = new OSCMessage("/ts/0", args);
		final byte[] answer
				= { 47, 116, 115, 47, 48, 0, 0, 0, 44, 116, 0, 0, -125, -86, -116, -112, 0, 0, 0, 0
				};
		final byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswer(result, answer);
	}

	@Test
	public void testArgumentTimestamp2000() {
		final List<Object> args = new ArrayList<>(1);
		final Calendar calendar = createCalendar();
		calendar.clear();
		calendar.set(2000, Calendar.JANUARY, 0);
		args.add(OSCTimeTag64.valueOf(calendar.getTime()));
		final OSCMessage message = new OSCMessage("/ts/2000", args);
		final byte[] answer
				= { 47, 116, 115, 47, 50, 48, 48, 48, 0, 0, 0, 0, 44, 116, 0, 0, -68, 22, 126, -112,
					0, 0, 0, 0 };
		final byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswer(result, answer);
	}

	@Test
	public void testArgumentTimestampAfterFeb2036() {
		final List<Object> args = new ArrayList<>(1);
		final Calendar calendar = createCalendar();
		calendar.clear();
		calendar.set(2037, Calendar.JANUARY, 0);
		args.add(OSCTimeTag64.valueOf(calendar.getTime()));
		final OSCMessage message = new OSCMessage("/ts/afterFeb2036", args);
		final byte[] answer
				= { 47, 116, 115, 47, 97, 102, 116, 101, 114, 70, 101, 98, 50, 48, 51, 54, 0, 0, 0,
					0, 44, 116, 0, 0, 1, -80, 31, 16, 0, 0, 0, 0 };
		final byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswer(result, answer);
	}

	@Test
	public void testAtLeastOneZeroAfterAddressAndTypesAndArgumentStrings() {
		final List<Object> args = new ArrayList<>(3);
		// We add 3 arguments.
		// Together with the comma before the types,
		// this creates a 4 byte aligned stream again (",sii").
		// In order to separate the types from the argument data,
		// an other four zeros have to appear on the stream.
		// This is what we check for here.
		//
		// We also test for at least one zero after argument strings here (8 % 4 = 0)
		args.add("iiffsstt"); // This would be interpreted as a continuation of the types if they were not zero terminated.
		args.add(-2);
		args.add(-3);
		// We do the same with the address.
		final OSCMessage message = new OSCMessage("/ZAT", args);
		final byte[] answer = {
			47, 90, 65, 84, 0, 0, 0, 0,
			44, 115, 105, 105, 0, 0, 0, 0,
			105, 105, 102, 102, 115, 115, 116, 116, 0, 0, 0, 0,
			-1, -1, -1, -2,
			-1, -1, -1, -3 };
		final byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswer(result, answer);
	}

	@Test
	public void testArgumentTrue() {
		final List<Object> args = new ArrayList<>(1);
		args.add(true);
		final OSCMessage message = new OSCMessage("/true", args);
		final byte[] answer = { 47, 116, 114, 117, 101, 0, 0, 0, 44, 84, 0, 0 };
		final byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswer(result, answer);
	}

	@Test
	public void testArgumentFalse() {
		final List<Object> args = new ArrayList<>(1);
		args.add(false);
		final OSCMessage message = new OSCMessage("/false", args);
		final byte[] answer = { 47, 102, 97, 108, 115, 101, 0, 0, 44, 70, 0, 0 };
		final byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswer(result, answer);
	}

	@Test
	public void testArgumentNull() {
		final List<Object> args = new ArrayList<>(1);
		args.add(null);
		final OSCMessage message = new OSCMessage("/null", args);
		final byte[] answer = { 47, 110, 117, 108, 108, 0, 0, 0, 44, 78, 0, 0 };
		final byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswer(result, answer);
	}

	@Test
	public void testDecreaseVolume() {
		List<Object> args = new ArrayList<>(2);
		args.add(1);
		args.add(0.2f);
		OSCMessage message = new OSCMessage("/sc/mixer/volume", args);
		byte[] answer = {
			47, 115, 99, 47, 109, 105, 120, 101, 114, 47, 118, 111,
			108, 117, 109, 101, 0, 0, 0, 0, 44, 105, 102, 0, 0, 0, 0,
			1, 62, 76, -52, -51 };
		byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswer(result, answer);
	}

	/**
	 * See the comment in
	 * {@link OSCSerializerTest#testPrintFloat2OnStream}.
	 */
	@Test
	public void testIncreaseVolume() {
		List<Object> args = new ArrayList<>(2);
		args.add(1);
		args.add(1.0f);
		OSCMessage message = new OSCMessage("/sc/mixer/volume", args);
		byte[] answer =	{
			47, 115, 99, 47, 109, 105, 120, 101, 114, 47, 118, 111, 108,
			117, 109, 101, 0, 0, 0, 0, 44, 105, 102, 0, 0, 0, 0, 1,	63,
			(byte) 128, 0, 0};
		byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswer(result, answer);
	}

	@Test
	public void testRun() {
		OSCMessage message = new OSCMessage("/sc/run");
		byte[] answer = {47, 115, 99, 47, 114, 117, 110, 0, 44, 0, 0, 0};
		byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswerOptionalComma(result, answer);
	}

	@Test
	public void testStop() {
		OSCMessage message = new OSCMessage("/sc/stop");
		byte[] answer = {47, 115, 99, 47, 115, 116, 111, 112, 0, 0, 0, 0, 44, 0, 0, 0};
		byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswerOptionalComma(result, answer);
	}

	@Test
	public void testCreateSynth() {

		final List<Object> origArguments = new ArrayList<>(3);
		origArguments.add(1001);
		origArguments.add("freq");
		origArguments.add(440.0f);
		OSCMessage message = new OSCMessage("/s_new", origArguments);
		byte[] answer
				= {0x2F, 0x73, 0x5F, 0x6E, 0x65, 0x77, 0, 0, 0x2C, 0x69, 0x73, 0x66, 0, 0, 0, 0, 0,
					0, 0x3, (byte) 0xE9, 0x66, 0x72, 0x65, 0x71, 0, 0, 0, 0, 0x43, (byte) 0xDC, 0,
					0};
		byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswer(result, answer);
	}

	@Test
	public void testArgumentCollectionsMixed() {
		final List<Object> args = new ArrayList<>(5);
		final Collection<Integer> singleType = new LinkedHashSet<>();
		singleType.add(-1);
		singleType.add(0);
		singleType.add(1);
		singleType.add(2);
		singleType.add(-1); // double entry; discarded because we have a Set
		singleType.add(99);
		final List<Integer> singleTypeList = new ArrayList<>(singleType);
		final List<Object> allTypes = new LinkedList<>();
		allTypes.add(null);
		allTypes.add(Boolean.TRUE);
		allTypes.add(Boolean.FALSE);
		allTypes.add(OSCImpulse.INSTANCE);
		allTypes.add(1);
		allTypes.add(1.0f);
		allTypes.add(1.0);
		allTypes.add(new byte[] { -99, -1, 0, 1, 99 });
		allTypes.add(1L);
		allTypes.add('h');
		allTypes.add("hello world!");
		allTypes.add(OSCTimeTag64.valueOf(new Date(0L)));
		args.add("firstArg");
		args.add(singleTypeList);
		args.add("middleArg");
		args.add(allTypes);
		args.add("lastArg");
		final OSCMessage message = new OSCMessage("/collectionsMixed", args);
		final byte[] answer
				= { 47, 99, 111, 108, 108, 101, 99, 116, 105, 111, 110, 115, 77, 105, 120, 101, 100,
					0, 0, 0, 44, 115, 91, 105, 105, 105, 105, 105, 93, 115, 91, 78, 84, 70, 73, 105,
					102, 100, 98, 104, 99, 115, 116, 93, 115, 0, 0, 0, 102, 105, 114, 115, 116, 65,
					114, 103, 0, 0, 0, 0, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0,
					0, 99, 109, 105, 100, 100, 108, 101, 65, 114, 103, 0, 0, 0, 0, 0, 0, 1, 63,
					-128, 0, 0, 63, -16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, -99, -1, 0, 1, 99, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 104, 104, 101, 108, 108, 111, 32, 119, 111,
					114, 108, 100, 33, 0, 0, 0, 0, -125, -86, -116, -112, 0, 0, 0, 0, 108, 97, 115,
					116, 65, 114, 103, 0 };
		final byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswer(result, answer);
	}

	@Test
	public void testArgumentCollectionsRecursiveMixed() {
		final List<Object> args = new ArrayList<>(5);
		final Collection<Object> fourthLevel = new LinkedList<>();
		fourthLevel.add(null);
		fourthLevel.add(Boolean.TRUE);
		fourthLevel.add(Boolean.FALSE);
		fourthLevel.add(OSCImpulse.INSTANCE);
		fourthLevel.add(1);
		fourthLevel.add(1.0f);
		fourthLevel.add(1.0);
		fourthLevel.add(new byte[] { -99, -1, 0, 1, 99 });
		fourthLevel.add(1L);
		fourthLevel.add('h');
		fourthLevel.add("hello world!");
		fourthLevel.add(OSCTimeTag64.valueOf(new Date(0L)));
		final Collection<Object> thirdLevel = new LinkedList<>();
		thirdLevel.add(fourthLevel);
		thirdLevel.add(-1);
		thirdLevel.add('h');
		thirdLevel.add(9.9);
		final Collection<Object> secondLevel = new LinkedList<>();
		secondLevel.add(0);
		secondLevel.add(thirdLevel);
		secondLevel.add('e');
		secondLevel.add(8.8);
		final Collection<Object> firstLevel = new LinkedList<>();
		firstLevel.add(1);
		firstLevel.add('l');
		firstLevel.add(secondLevel);
		firstLevel.add(7.7);
		args.add("firstArg");
		args.add(firstLevel);
		args.add("lastArg");
		final OSCMessage message = new OSCMessage("/collectionsRecursive", args);
		final byte[] answer
				= { 47, 99, 111, 108, 108, 101, 99, 116, 105, 111, 110, 115, 82, 101, 99, 117, 114,
					115, 105, 118, 101, 0, 0, 0, 44, 115, 91, 105, 99, 91, 105, 91, 91, 78, 84, 70,
					73, 105, 102, 100, 98, 104, 99, 115, 116, 93, 105, 99, 100, 93, 99, 100, 93,
					100, 93, 115, 0, 0, 0, 0, 102, 105, 114, 115, 116, 65, 114, 103, 0, 0, 0, 0, 0,
					0, 0, 1, 0, 0, 0, 108, 0, 0, 0, 0, 0, 0, 0, 1, 63, -128, 0, 0, 63, -16, 0, 0, 0,
					0, 0, 0, 0, 0, 0, 5, -99, -1, 0, 1, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
					0, 104, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, 0, 0, 0, 0,
					-125, -86, -116, -112, 0, 0, 0, 0, -1, -1, -1, -1, 0, 0, 0, 104, 64, 35, -52,
					-52, -52, -52, -52, -51, 0, 0, 0, 101, 64, 33, -103, -103, -103, -103, -103,
					-102, 64, 30, -52, -52, -52, -52, -52, -51, 108, 97, 115, 116, 65, 114, 103,
					0 };
		final byte[] result = convertMessageToByteArray(message);
		checkResultEqualsAnswer(result, answer);
	}

	@Test
	public void testEncodeLong() throws OSCParseException {
		Long one001 = 1001L;
		final List<?> origArguments = Collections.singletonList(one001);
		OSCMessage message = new OSCMessage("/dummy", origArguments);
		byte[] byteArray = convertMessageToByteArray(message);
		OSCParser converter = new OSCSerializerAndParserBuilder().buildParser();
		final ByteBuffer bytes = ByteBuffer.wrap(byteArray).asReadOnlyBuffer();
		final OSCMessage packet = (OSCMessage) converter.convert(bytes);
		if (!packet.getAddress().equals("/dummy")) {
			Assert.fail("Send Big Integer did not receive the correct address");
		}
		List<?> arguments = packet.getArguments();
		if (arguments.size() != 1) {
			Assert.fail("Send Big Integer should have 1 argument, not " + arguments.size());
		}
		if (!(arguments.get(0) instanceof Long)) {
			Assert.fail("arguments.get(0) should be a Long, not " + arguments.get(0).getClass());
		}
		if (!(new Long(1001L).equals(arguments.get(0)))) {
			Assert.fail("Instead of Long(1001), received " + arguments.get(0));
		}
	}

	@Test
	public void testEncodeArray() throws OSCParseException {
		final List<Float> floats = new ArrayList<>(2);
		floats.add(10.0f);
		floats.add(100.0f);
		final List<?> origArguments = Collections.singletonList(floats);
		final OSCMessage message = new OSCMessage("/dummy", origArguments);
		final byte[] byteArray = convertMessageToByteArray(message);
		final OSCParser converter = new OSCSerializerAndParserBuilder().buildParser();
		final ByteBuffer bytes = ByteBuffer.wrap(byteArray).asReadOnlyBuffer();
		final OSCMessage packet = (OSCMessage) converter.convert(bytes);
		if (!packet.getAddress().equals("/dummy")) {
			Assert.fail("We did not receive the correct address");
		}
		final List<?> arguments = packet.getArguments();
		if (arguments.size() != origArguments.size()) {
			Assert.fail("We should have received " + origArguments + " arguments, not "
					+ arguments.size());
		}
		final Object firstArgument = arguments.get(0);
		if (!(firstArgument instanceof List)) {
			Assert.fail("arguments.get(0) should be a List, not "
					+ ((firstArgument == null)
							? String.valueOf((Object) null)
							: firstArgument.getClass().toString()));
		}
		// We can safely suppress the warning, as we already made sure the cast will not fail.
		@SuppressWarnings("unchecked") final List<Object> theArray = (List<Object>) firstArgument;
		if (theArray.size() != floats.size()) {
			Assert.fail("arguments.get(0) should be a List of size " + floats.size() + " not "
					+ theArray.size());
		}
		for (int i = 0; i < floats.size(); ++i) {
			if (!floats.get(i).equals(theArray.get(i))) {
				Assert.fail("List element " + i + " should be " + floats.get(i) + " not "
						+ theArray.get(i));
			}
		}
	}

	@Test
	public void testAddressValidationFrontendConstructorNull() {

		// expect no exception, as we could still set a valid address later on
		expectedException.expect(IllegalArgumentException.class);
		OSCMessage oscMessage = new OSCMessage(null);
	}

	@Test
	public void testAddressValidationFrontendConstructorValid() {

		// expect no exception, as the address is valid
		OSCMessage oscMessage = new OSCMessage("/hello/world");
	}

	@Test
	public void testAddressValidationFrontendConstructorInvalid() {

		expectedException.expect(IllegalArgumentException.class);
		OSCMessage oscMessage = new OSCMessage("/ hello/world");
	}

	@Test
	public void testAddressValidation() {
		Assert.assertFalse(OSCMessage.isValidAddress(null));
		Assert.assertFalse(OSCMessage.isValidAddress(""));
		Assert.assertFalse(OSCMessage.isValidAddress("hello/world"));
		Assert.assertFalse(OSCMessage.isValidAddress("/ hello/world"));
		Assert.assertFalse(OSCMessage.isValidAddress("/#hello/world"));
		Assert.assertFalse(OSCMessage.isValidAddress("/*hello/world"));
		Assert.assertFalse(OSCMessage.isValidAddress("/,hello/world"));
		Assert.assertFalse(OSCMessage.isValidAddress("/?hello/world"));
		Assert.assertFalse(OSCMessage.isValidAddress("/[hello/world"));
		Assert.assertFalse(OSCMessage.isValidAddress("/]hello/world"));
		Assert.assertFalse(OSCMessage.isValidAddress("/{hello/world"));
		Assert.assertFalse(OSCMessage.isValidAddress("/}hello/world"));
		Assert.assertFalse(OSCMessage.isValidAddress("//hello/world"));
		Assert.assertFalse(OSCMessage.isValidAddress("#abc"));
		Assert.assertFalse(OSCMessage.isValidAddress("#replyx"));
		Assert.assertFalse(OSCMessage.isValidAddress("#replx"));
		Assert.assertTrue( OSCMessage.isValidAddress("/hello"));
		Assert.assertTrue( OSCMessage.isValidAddress("/hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("/hello/world/two"));
		Assert.assertTrue( OSCMessage.isValidAddress("/123/world/two"));
		Assert.assertTrue( OSCMessage.isValidAddress("/!hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("/~hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("/`hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("/@hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("/$hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("/%hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("/€hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("/^hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("/&hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("/(hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("/)hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("/-hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("/_hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("/+hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("/=hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("/.hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("/<hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("/>hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("/;hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("/:hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("/'hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("/\"hello/world"));
		Assert.assertTrue( OSCMessage.isValidAddress("#reply"));
	}
} public interface OSCPacket extends Serializable {

} public interface OSCPacketListener {

	/**
	 * Process an incoming packet.
	 * @param event contains the packet content and meta-data
	 */
	void handlePacket(final OSCPacketEvent event);

	/**
	 * Process a bad data event.
	 * @param event the bad data event to handle
	 */
	void handleBadData(final OSCBadDataEvent event);
} public class OSCSerializeException extends Exception {

	public OSCSerializeException() {
		super();
	}

	public OSCSerializeException(final String message) {
		super(message);
	}

	public OSCSerializeException(final String message, final Throwable cause) {
		super(message, cause);
	}

	public OSCSerializeException(final Throwable cause) {
		super(cause);
	}
} public class SimpleOSCMessageListener implements OSCMessageListener {

	private int messageReceivedCount;
	private OSCMessageEvent event;

	public SimpleOSCMessageListener() {

		this.messageReceivedCount = 0;
		this.event = null;
	}
	public boolean isMessageReceived() {
		return (messageReceivedCount > 0);
	}

	@SuppressWarnings("WeakerAccess")
	public int getMessageReceivedCount() {
		return messageReceivedCount;
	}

	public OSCMessageEvent getReceivedEvent() {
		return event;
	}

	@Override
	public void acceptMessage(final OSCMessageEvent event) {
		messageReceivedCount++;
		this.event = event;
	}
} public class SimpleOSCPacketListener implements OSCPacketListener {

	private int packetReceivedCount;
	private OSCPacket packet;

	public SimpleOSCPacketListener() {

		this.packetReceivedCount = 0;
		this.packet = null;
	}
	public boolean isMessageReceived() {
		return (packetReceivedCount > 0);
	}

	@SuppressWarnings("WeakerAccess")
	public int getMessageReceivedCount() {
		return packetReceivedCount;
	}

	public OSCPacket getReceivedPacket() {
		return packet;
	}

	@Override
	public void handlePacket(final OSCPacketEvent event) {
		packetReceivedCount++;
		this.packet = event.getPacket();
	}

	@Override
	public void handleBadData(final OSCBadDataEvent event) {}
} public class OSCTimeTag64 implements Cloneable, Serializable, Comparable<OSCTimeTag64> {

	// Public API
	/**
	 * OSC epoch length in milliseconds.
	 * An epoch is the maximum range of a 64bit OSC Time-tag,
	 * which is (uint32_max + 1) * 1000 milliseconds.
	 */
	@SuppressWarnings("WeakerAccess")
	public static final long EPOCH_LENGTH_JAVA_TIME = 0x100000000L * 1000L;
	/**
	 * Start of the first epoch expressed in Java time
	 * (as used by {@link Date#Date(long)}
	 * and {@link Date#getTime()}).
	 * This is "1-Jan-1900 @ 00:00:00", and we use UTC as time-zone.
	 * Dates before this can not be represented with an OSC Time-tag.
	 */
	public static final long EPOCH_START_JAVA_TIME_0 = -2208992400000L;
	// Public API
	/**
	 * Start of the current epoch expressed in Java time.
	 */
	@SuppressWarnings("WeakerAccess")
	public static final long EPOCH_START_JAVA_TIME_CURRENT
			= findEpochStartJavaTime(new Date().getTime());
	// Public API
	/**
	 * The OSC time-tag with the semantics of "immediately"/"now".
	 */
	@SuppressWarnings("WeakerAccess")
	public static final long IMMEDIATE_RAW = 0x1L;
	/**
	 * The OSC time-tag with the semantics of "immediately"/"now".
	 * @see #immediateDate()
	 */
	public static final OSCTimeTag64 IMMEDIATE = valueOf(IMMEDIATE_RAW);
	/**
	 * Filter for the 32 lower/least-significant bits of a long.
	 */
	private static final long FILTER_LOWER_32 = 0xFFFFFFFFL;
	/**
	 * Number of bits for storing the "seconds" value in NTP time.
	 */
	private static final int NTP_SECONDS_BITS = 32;
	/**
	 * The Factor to multiply `fractions` with, to get to the number of nano-seconds.
	 * It is calculated as <code>10^9 / 2^32</code>.
	 */
	private static final double FRACTION_TO_NANOS_MULTIPLIER = 0.23283;
	private static final long serialVersionUID = 1L;

	/**
	 * The Time-tags value as specified by the OSC protocol standard.
	 * It is treated as a 64bit unsigned integer,
	 * with the higher-order 32bit representing the whole seconds
	 * from the beginning of the epoch,
	 * and the lower-order 32bit representing the fractions of a second
	 * from the beginning of the last full second.
	 */
	private final long ntpTime;

	protected OSCTimeTag64(final long ntpTime) {
		this.ntpTime = ntpTime;
	}

	/**
	 * Returns the OSC/NTP conform <i>Time-tag</i> value.
	 * @return 64bits:
	 *   32 higher bits (including MSB): seconds,
	 *   32 lower bits: fraction (of a second)
	 */
	public long getNtpTime() {
		return ntpTime;
	}

	// Public API
	/**
	 * Returns the number of whole seconds from the start of the epoch
	 * of this time-tag.
	 * @return high-order 32-bit unsigned value representing the "seconds" part
	 *   of this OSC <i>Time-tag</i>
	 */
	@SuppressWarnings("WeakerAccess")
	public long getSeconds() {
		return ntpTime >>> NTP_SECONDS_BITS;
	}

	// Public API
	/**
	 * Returns the fraction of a second from the start of the epoch + seconds
	 * of this time-tag.
	 * Denotes a number of seconds * (1 / 2^32),
	 * which allows to specify to a precision of about 233 pico seconds.
	 * @return lower-order 32-bit unsigned value representing the "fraction"
	 *   part of this OSC <i>Time-tag</i>
	 */
	@SuppressWarnings("WeakerAccess")
	public long getFraction() {
		return ntpTime & FILTER_LOWER_32;
	}

	// Public API
	/**
	 * Returns the rounded number of nanoseconds from the start of the epoch + seconds
	 * of this time-tag.
	 * @return 32-bit unsigned value representing the nano-seconds after the last full second
	 *   of this OSC <i>Time-tag</i>
	 */
	@SuppressWarnings("WeakerAccess")
	public int getNanos() {
		return (int) Math.round(getFraction() * FRACTION_TO_NANOS_MULTIPLIER);
	}

	public boolean isImmediate() {
		return ntpTime == IMMEDIATE_RAW;
	}

	@Override
	public boolean equals(final Object other) {

		return (other instanceof OSCTimeTag64)
				&& (getNtpTime() == ((OSCTimeTag64) other).getNtpTime());
	}

	@Override
	public int hashCode() {
		int hash = 5;
		hash = (89 * hash) + (int) (this.ntpTime ^ (this.ntpTime >>> 32));
		return hash;
	}

	@Override
	public int compareTo(final OSCTimeTag64 other) {

		return (10 * Long.signum(Long.compare(getSeconds(), other.getSeconds())))
				+ Long.signum(Long.compare(getFraction(), other.getFraction()));
	}

	@Override
	public OSCTimeTag64 clone() throws CloneNotSupportedException {
		return (OSCTimeTag64) super.clone();
	}

	/**
	 * Returns the Java date closest to this time-tags value.
	 * @param epochIndicatorTime the resulting date will be
	 *   in the same OSC epoch as this Java date-stamp
	 * @return this time-tags value rounded to the closest full millisecond
	 */
	public Date toDate(final long epochIndicatorTime) {
		return new Date(toJavaTime(epochIndicatorTime));
	}

	/**
	 * Returns the Java date closest to this time-tags value.
	 * @param epochIndicator the resulting date will be
	 *   in the same OSC epoch as this
	 * @return this time-tags value rounded to the closest full millisecond
	 */
	public Date toDate(final Date epochIndicator) {
		return (epochIndicator == null)
				? toDate()
				: toDate(epochIndicator.getTime());
	}

	/**
	 * Returns the Java date closest to this time-tags value.
	 * The resulting date will be in the current OSC epoch.
	 * @return this time-tags value rounded to the closest full millisecond
	 */
	private Date toDate() {
		return new Date(toJavaTimeInEpoch(EPOCH_START_JAVA_TIME_CURRENT));
	}

	private static long findEpochStartJavaTime(final long javaTime) {

		final long epochIndex = (javaTime - EPOCH_START_JAVA_TIME_0) / EPOCH_LENGTH_JAVA_TIME;
		return EPOCH_START_JAVA_TIME_0 + (epochIndex * EPOCH_LENGTH_JAVA_TIME);
	}

	private long toJavaTimeInEpoch(final long epochStart) {

		final long secondsInMs = getSeconds() * 1000L;
		// use round-off on the fractional part to preserve going to lower precision
		final long fractionInMs = Math.round((1000D * getFraction()) / 0x100000000L);

		return epochStart + (secondsInMs | fractionInMs);
	}

	private long toJavaTime(final Long epochIndicatorTime) {

		final long epochStart;
		if (epochIndicatorTime == null) {
			epochStart = EPOCH_START_JAVA_TIME_CURRENT;
		} else {
			epochStart = findEpochStartJavaTime(epochIndicatorTime);
		}

		return toJavaTimeInEpoch(epochStart);
	}

	private static long toNtpTimeTag(final long seconds, final long fraction) {
		return (seconds << NTP_SECONDS_BITS) | fraction;
	}

	/**
	 * Returns the Java instant closest to this time-tags value.
	 * {@link Instant} covers the range of the OSC time tag,
	 * and nearly covers the precision
	 * (1 nanosecond accuracy instead of 233 picoseconds).
	 * @see #valueOf(Instant)
	 * @return this time-tags value rounded to the closest full nanosecond
	 */
	public Instant toInstant() {
		return Instant.ofEpochSecond(getSeconds(), getNanos());
	}

	/**
	 * Returns the Java instant closest to this time-tags value.
	 *  {@link Instant} covers the range of the OSC time tag,
	 *  and nearly covers the precision
	 *  (1 nanosecond accuracy instead of 233 picoseconds).
	 * @param instant to be converted to an OSC time tag
	 * @see #toInstant()
	 * @return the closest
	 */
	public static OSCTimeTag64 valueOf(final Instant instant) {
		return valueOf(instant.getEpochSecond(), Math.round(instant.getNano() / FRACTION_TO_NANOS_MULTIPLIER));
	}

	public static OSCTimeTag64 valueOf(final long ntpTime) {
		return new OSCTimeTag64(ntpTime);
	}

	public static OSCTimeTag64 valueOf(final long seconds, final long fraction) {
		return new OSCTimeTag64(toNtpTimeTag(seconds, fraction));
	}

	public static OSCTimeTag64 valueOf(final Date javaTime) {
		return new OSCTimeTag64(javaToNtpTimeStamp(javaTime.getTime()));
	}

	@Override
	public String toString() {
		return toDate().toString();
	}

	/**
	 * Converts a Java time-stamp (milliseconds since 1970)
	 * to a 64-bit OSC time representation.
	 * This code was copied from the "Apache Jakarta Commons - Net" library,
	 * which is licensed under the
	 * <a href="http://www.apache.org/licenses/LICENSE-2.0.html">
	 * Apache-2.0</a>.
	 * The original source file can be found
	 * <a href="http://svn.apache.org/viewvc/commons/proper/net/trunk/src/main/java/org/apache/commons/net/ntp/TimeStamp.java?view=co">
	 * here</a>.
	 * @param javaTime Java time-stamp, as returned by {@link Date#getTime()}
	 * @return OSC time-tag representation of the Java time value.
	 */
	private static long javaToNtpTimeStamp(final long javaTime) {

		final long epochStart = findEpochStartJavaTime(javaTime);
		final long millisecondsInEpoch = javaTime - epochStart;
		final long seconds = millisecondsInEpoch / 1000L;
		final long fraction = Math.round(((millisecondsInEpoch % 1000L) * 0x100000000L) / 1000D);

		return toNtpTimeTag(seconds, fraction);
	}

	/**
	 * Creates the Java representation of an OSC time-tag with the semantics of
	 * "immediately"/"now".
	 *
	 * @return the created Java <code>Date</code>
	 * @see #IMMEDIATE
	 */
	public static Date immediateDate() {
		return IMMEDIATE.toDate(new Date(EPOCH_START_JAVA_TIME_0));
	}
} public class OSCPatternAddressMessageSelector implements MessageSelector {

	private final List<String> patternParts;

	/**
	 * Creates a selector that may take a simple address or a wildcard as matching criteria.
	 *
	 * @param selector either a fixed address like "/sc/mixer/volume",
	 *   or a selector pattern (a mix between wildcards and regex)
	 *   like "/??/mixer/*", see {@link OSCPatternAddressMessageSelector the class comment}
	 *   for more details
	 */
	public OSCPatternAddressMessageSelector(final String selector) {
		this.patternParts = splitIntoParts(selector);
	}

	@Override
	public boolean equals(final Object other) {

		boolean equal = false;
		if (other instanceof OSCPatternAddressMessageSelector) {
			final OSCPatternAddressMessageSelector otherSelector
					= (OSCPatternAddressMessageSelector) other;
			equal = this.patternParts.equals(otherSelector.patternParts);
		}

		return equal;
	}

	@Override
	public int hashCode() {
		return this.patternParts.hashCode();
	}

	@Override
	public boolean isInfoRequired() {
		return false;
	}

	@Override
	public boolean matches(final OSCMessageEvent messageEvent) {

		final List<String> messageAddressParts = splitIntoParts(messageEvent.getMessage().getAddress());
		return matches(patternParts, 0, messageAddressParts, 0);
	}

	/**
	 * Splits an OSC message address or address selector pattern into parts that are convenient
	 * during the matching process.
	 * @param addressOrPattern to be split into parts, e.g.: "/hello/", "/hello//world//"
	 * @return the given address or pattern split into parts: {"hello"}, {"hello, "", "world", ""}
	 */
	private static List<String> splitIntoParts(final String addressOrPattern) {

		final List<String> parts
				= new ArrayList<>(Arrays.asList(addressOrPattern.split("/", -1)));
		if (addressOrPattern.startsWith("/")) {
			// as "/hello" gets split into {"", "hello"}, we remove the first empty entry,
			// so we end up with {"hello"}
			parts.remove(0);
		}
		if (addressOrPattern.endsWith("/")) {
			// as "hello/" gets split into {"hello", ""}, we also remove the last empty entry,
			// so we end up with {"hello"}
			parts.remove(parts.size() - 1);
		}
		return Collections.unmodifiableList(parts);
	}

	/**
	 * Tries to match an OSC <i>Address Pattern</i> to a selector,
	 * both already divided into their parts.
	 * @param patternParts all the parts of the pattern
	 * @param patternPartIndex index/pointer to the current part of the pattern we are looking at
	 * @param messageAddressParts all the parts of the address
	 * @param addressPartIndex index/pointer to the current part of the address we are looking at
	 * @return true if the address matches, false otherwise
	 */
	private static boolean matches(
			final List<String> patternParts,
			final int patternPartIndex,
			final List<String> messageAddressParts,
			final int addressPartIndex)
	{
		int ppi = patternPartIndex;
		int api = addressPartIndex;
		while (ppi < patternParts.size()) {
			// There might be some path-traversal wildcards (PTW) "//" in the pattern.
			// "//" in the pattern translates to an empty String ("") in the pattern parts.
			// We skip all consecutive "//"s at the current pattern position.
			boolean pathTraverser = false;
			while ((ppi < patternParts.size()) && patternParts.get(ppi).isEmpty()) {
				ppi++;
				pathTraverser = true;
			}
			// ppi is now at the end, or at the first non-PTW part
			if (pathTraverser) {
				if (ppi == patternParts.size()) {
					// trailing PTW matches the whole rest of the address
					return true;
				}
				while (api < messageAddressParts.size()) {
					if (matches(messageAddressParts.get(api), patternParts.get(ppi))
							&& matches(patternParts, ppi + 1, messageAddressParts, api + 1))
					{
						return true;
					}
					api++;
				}
				// end of address parts reached, but there are still non-PTW pattern parts
				// left
				return false;
			} else {
				if ((ppi == patternParts.size()) != (api == messageAddressParts.size())) {
					// end of pattern, no trailing PTW, but there are still address parts left
					// OR
					// end of address, but there are still non-PTW pattern parts left
					return false;
				}
				if (!matches(messageAddressParts.get(api), patternParts.get(ppi))) {
					return false;
				}
				api++;
			}
			ppi++;
		}

		return (api == messageAddressParts.size());
	}

	// Public API
	/**
	 * Tries to match an OSC <i>Address Pattern</i> part to a part of
	 * a selector.
	 * This code was copied and adapted from LibLo,
	 * and is licensed under the Public Domain.
	 * For more details see:
	 * {@link OSCPatternAddressMessageSelector the class comment}.
	 * @param str address part
	 * @param p pattern part
	 * @return true if the address part matches, false otherwise
	 */
	@SuppressWarnings("WeakerAccess")
	public static boolean matches(final String str, final String p) {

		boolean match;
		char c;

		int si = 0;
		int pi = 0;
		while (pi < p.length()) {
			if ((si == str.length()) && (p.charAt(pi) != '*')) {
				return false;
			}

			c = p.charAt(pi++);
			boolean negate;
			switch (c) {
				case '*':
					while ((pi < p.length()) && (p.charAt(pi) == '*') && (p.charAt(pi) != '/')) {
						pi++;
					}

					if (pi == p.length()) {
						return true;
					}

//					if (p.charAt(pi) != '?' && p.charAt(pi) != '[' && p.charAt(pi) != '\\')
					if ((p.charAt(pi) != '?') && (p.charAt(pi) != '[') && (p.charAt(pi) != '{')) {
						while ((si < str.length()) && (p.charAt(pi) != str.charAt(si))) {
							si++;
						}
					}

					while (si < str.length()) {
						if (matches(str.substring(si), p.substring(pi))) {
							return true;
						}
						si++;
					}
					return false;

				case '?':
					if (si < str.length()) {
						break;
					}
					return false;

				/*
				 * set specification is inclusive, that is [a-z] is a, z and
				 * everything in between. this means [z-a] may be interpreted
				 * as a set that contains z, a and nothing in between.
				 */
				case '[':
					if (p.charAt(pi) == '!') {
						negate = true;
						pi++;
					} else {
						negate = false;
					}

					match = false;

					while (!match && (pi < p.length())) {
						c = p.charAt(pi++);
						if (pi == p.length()) {
							return false;
						}
						if (p.charAt(pi) == '-') {
							// c-c
							pi++;
							if (pi == p.length()) {
								return false;
							}
							if (p.charAt(pi) != ']') {
								if ((str.charAt(si) == c) || (str.charAt(si) == p.charAt(pi))
										|| ((str.charAt(si) > c) && (str.charAt(si) < p.charAt(pi))))
								{
									match = true;
								}
							} else {
								// c-]
								if (str.charAt(si) >= c) {
									match = true;
								}
								break;
							}
						} else {
							// cc or c]
							if (c == str.charAt(si)) {
								match = true;
							}
							if (p.charAt(pi) != ']') {
								if (p.charAt(pi) == str.charAt(si)) {
									match = true;
								}
							} else {
								break;
							}
						}
					}

					if (negate == match) {
						return false;
					}
					// if there is a match, skip past the cSet and continue on
					while ((pi < p.length()) && (p.charAt(pi) != ']')) {
						pi++;
					}
					if (pi++ == p.length()) {
						// oops!
						return false;
					}
					break;

				// {aString,bString,cString}
				case '{':
					// p.charAt(pi) is now first character in the {brace list}

					// to back-track
					final int place = si;
					// to forward-track
					int remainder = pi;

					// find the end of the brace list
					while ((remainder < p.length()) && (p.charAt(remainder) != '}')) {
						remainder++;
					}
					if (remainder == p.length()) {
						// oops!
						return false;
					}
					remainder++;

					c = p.charAt(pi++);
					while (pi <= p.length()) {
						if (c == ',') {
							if (matches(str.substring(si), p.substring(remainder))) {
								return true;
							} else {
								// back-track on test string
								si = place;
								// continue testing,
								// skip comma
								if (pi++ == p.length()) {
									// oops!
									return false;
								}
							}
						} else if (c == '}') {
							// continue normal pattern matching
							if ((pi == p.length()) && (si == str.length())) {
								return true;
							}
							// "si" is incremented again at the end of the loop
							si--;
							break;
						} else if (c == str.charAt(si)) {
							si++;
							if ((si == str.length()) && (remainder < p.length())) {
								return false;
							}
						} else {
							// skip to next comma
							si = place;
							while ((pi < p.length()) && (p.charAt(pi) != ',')
									&& (p.charAt(pi) != '}'))
							{
								pi++;
							}
							if (pi < p.length()) {
								if (p.charAt(pi) == ',') {
									pi++;
								} else if (p.charAt(pi) == '}') {
									return false;
								}
							}
						}
						c = p.charAt(pi++);
					}
					break;

				/*
				 * Not part of OSC pattern matching
					case '\\':
						if (p.charAt(pi)) {
							c = p.charAt(pi)++;
						}
				 */
				default:
					if (c != str.charAt(si)) {
						return false;
					}
					break;
			}
			si++;
		}

		return (si == str.length());
	}
} public class TCPTransport implements Transport {

	private final InetSocketAddress local;
	private final InetSocketAddress remote;
	private final OSCParser parser;
	private final ByteArrayListBytesReceiver serializationBuffer;
	private final OSCSerializer serializer;
	private Socket clientSocket;
	private ServerSocket serverSocket;

	public TCPTransport(
			final InetSocketAddress local,
			final InetSocketAddress remote)
			throws IOException
	{
		this(local, remote, new OSCSerializerAndParserBuilder());
	}

	public TCPTransport(
			final InetSocketAddress local,
			final InetSocketAddress remote,
			final OSCSerializerAndParserBuilder builder)
			throws IOException
	{
		this.local = local;
		this.remote = remote;
		this.parser = builder.buildParser();
		this.serializationBuffer = new ByteArrayListBytesReceiver();
		this.serializer = builder.buildSerializer(serializationBuffer);
		this.clientSocket = null;
		this.serverSocket = null;
	}

	private Socket getClientSocket() throws IOException {

		if ((clientSocket == null) || clientSocket.isClosed()) {
			clientSocket = new Socket();
		}

		return clientSocket;
	}

	private ServerSocket getServerSocket() throws IOException {
		if ((serverSocket == null) || serverSocket.isClosed()) {
			serverSocket = new ServerSocket();
			serverSocket.setReuseAddress(true);
			serverSocket.bind(local);
		}

		return serverSocket;
	}

	@Override
	public void connect() throws IOException {
		// Not relevant for TCP. TCP does involve connections, but we create them as
		// we need them, when either `send` or `receive` is invoked.
	}

	@Override
	public void disconnect() throws IOException {
		// Not possible/relevant for TCP.
	}

	@Override
	public boolean isConnected() {
		// Not relevant for TCP.
		return false;
	}

	public boolean isListening() throws IOException {

		boolean listening;
		try {
			new Socket(local.getAddress(), local.getPort()).close();
			listening = true;
		} catch (ConnectException cex) {
			listening = false;
		}

		return listening;
	}

	/**
	 * Close the socket and free-up resources.
	 * It is recommended that clients call this when they are done with the port.
	 * @throws IOException If an I/O error occurs
	 */
	@Override
	public void close() throws IOException {
		if (clientSocket != null) {
			clientSocket.close();
		}

		if (serverSocket != null) {
			serverSocket.close();
		}
	}

	@Override
	public void send(final OSCPacket packet)
			throws IOException, OSCSerializeException
	{
		serializer.write(packet);

		final Socket clientSock = getClientSocket();
		if (!clientSock.isConnected()) {
			clientSock.connect(remote);
		}

		// Closing the output stream is necessary in order for the receiving side to
		// know that it has received everything. When reading bytes off of the
		// stream, an EOF (-1) byte signals the end of the stream, and this doesn't
		// get sent until the output stream is closed.
		//
		// NB: Closing the output stream effectively closes the client socket as
		// well. The next time getClientSocket() is called, it will recognize that
		// the socket is closed and create a new one. So, every message sent uses a
		// new client socket.
		try (OutputStream out = clientSock.getOutputStream()) {
			serializationBuffer.writeTo(out);
		}
	}

	// InputStream.readAllBytes is available as of Java 9. Implementing it here in
	// order to support Java 8.
	//
	// Source: https://stackoverflow.com/a/53347936/2338327
	//
	// I modified the source in that we don't want to close the input stream, as
	// it belongs to a ServerSocket and we don't want to close the stream because
	// that would also close the socket.
	private byte[] readAllBytes(final InputStream inputStream)
			throws IOException
	{
		try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
			// 4 * 0x400 = 4 KB
			final int bufLen = 4 * 0x400;
			int readLen;
			final byte[] buffer = new byte[bufLen];
			while ((readLen = inputStream.read(buffer, 0, bufLen)) != -1) {
				outputStream.write(buffer, 0, readLen);
			}

			return outputStream.toByteArray();
		}
	}

	@Override
	public OSCPacket receive() throws IOException, OSCParseException {

		final ServerSocket serverSock = getServerSocket();

		// Unlike UDP, TCP involves connections, and a valid use case is for a
		// client socket to connect to a server socket simply to test whether the
		// server socket is listening.
		//
		// When this happens, an empty byte array is received. We recognize this
		// scenario and move onto the next connection, and keep doing this until we
		// receive a connection that sends > 0 bytes.
		while (true) {
			final Socket dataSocket = serverSock.accept();
			final byte[] bytes = readAllBytes(dataSocket.getInputStream());

			if (bytes.length == 0) {
				continue;
			}

			return parser.convert(ByteBuffer.wrap(bytes));
		}
	}

	@Override
	public boolean isBlocking() {
		// Not relevant for TCP.
		return false;
	}

	@Override
	public String toString() {
		return String.format(
			"%s: local=%s, remote=%s", getClass().getSimpleName(), local, remote
		);
	}
}
import com.illposed.osc.OSCBundle;
import com.illposed.osc.OSCMessage;
import com.illposed.osc.OSCMessageTest;
import com.illposed.osc.OSCPacket;
import com.illposed.osc.OSCPacketListener;
import com.illposed.osc.OSCSerializeException;
import com.illposed.osc.SimpleOSCMessageListener;
import com.illposed.osc.SimpleOSCPacketListener;
import com.illposed.osc.argument.OSCTimeTag64;
import com.illposed.osc.messageselector.OSCPatternAddressMessageSelector;
import com.illposed.osc.transport.tcp.TCPTransport;
import java.io.IOException;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.TimeoutException;
import java.util.function.BooleanSupplier;
import java.util.List;
import java.util.Random;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.junit.After;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
		} catch (final IOException ex) {
			log.error("Failed to close test OSC out port", ex);
		}

		waitForSocketClose();
	}

	private void testSocketClose(NetworkProtocol protocol, int receiverPort)
			throws Exception {
		setUp(0, 0, receiverPort, receiverPort, null, protocol);

		receiver.close();
		sender.close();

		waitForSocketClose();

		// check if the underlying sockets were closed
		// NOTE We can have many (out-)sockets sending
		//   on the same address and port,
		//   but only one receiving per each such tuple.
		sender = new OSCPortOutBuilder()
				.setRemotePort(receiverPort)
				.setLocalPort(0)
				.setNetworkProtocol(protocol)
				.build();

		receiver = new OSCPortInBuilder()
				.setLocalPort(receiverPort)
				.setRemotePort(0)
				.setNetworkProtocol(protocol)
				.build();
	}

	@Test
	public void testSocketCloseUDP() throws Exception {
		testSocketClose(NetworkProtocol.UDP, OSCPort.defaultSCOSCPort());
	}

	@Test
	public void testSocketCloseTCP() throws Exception {
		testSocketClose(NetworkProtocol.TCP, findAvailablePort());
	}

	@Test
	public void testSocketAutoClose() throws Exception {
		setUp(
				0,
				0,
				OSCPort.defaultSCOSCPort(),
				OSCPort.defaultSCOSCPort(),
				null,
				NetworkProtocol.UDP
		);

		// DANGEROUS! here we forget to close the underlying sockets!
		receiver = null;
		sender = null;

		// make sure the old receiver is gone for good
		System.gc();
		Thread.sleep(WAIT_FOR_SOCKET_CLOSE_MS);

		// check if the underlying sockets were closed
		// NOTE We can have many (out-)sockets sending
		//   on the same address and port,
		//   but only one receiving per each such tuple.
		sender = new OSCPortOutBuilder()
				.setRemotePort(OSCPort.defaultSCOSCPort())
				.setLocalPort(0)
				.setNetworkProtocol(NetworkProtocol.UDP)
				.build();

		receiver = new OSCPortInBuilder()
				.setLocalPort(OSCPort.defaultSCOSCPort())
				.setRemotePort(0)
				.setNetworkProtocol(NetworkProtocol.UDP)
				.build();
	}

	private void assertMessageReceived(
			SimpleOSCMessageListener listener, int timeout) {
		assertEventuallyTrue(
				100,
				timeout,
				"Message was not received.",
				() -> listener.isMessageReceived()
		);
	}

	private void assertMessageNotReceived(
			SimpleOSCMessageListener listener, int timeout, String failMessage) {
		assertNeverTrue(
				100,
				timeout,
				failMessage,
				() -> listener.isMessageReceived()
		);
	}

	private void assertPacketReceived(
			SimpleOSCPacketListener listener, int timeout) {
		assertEventuallyTrue(
				100,
				timeout,
				"Packet was not received.",
				() -> listener.isMessageReceived()
		);
	}

	private void assertPacketNotReceived(
			SimpleOSCPacketListener listener, int timeout, String failMessage) {
		assertNeverTrue(
				100,
				timeout,
				failMessage,
				() -> listener.isMessageReceived()
		);
	}

	private void testReceivingImpl() throws Exception {
		OSCMessage message = new OSCMessage("/message/receiving");