/* * Copyright (c) 2015 NOVA, All rights reserved. * This library is free software, licensed under GNU Lesser General Public License version 3 * * This file is part of NOVA. * * NOVA is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * NOVA is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with NOVA. If not, see <http://www.gnu.org/licenses/>. */ package nova.core.network; import nova.core.entity.component.Player; import nova.core.retention.Data; import nova.core.retention.Storable; import org.apache.commons.math3.geometry.euclidean.threed.Vector3D; import org.apache.commons.math3.geometry.euclidean.twod.Vector2D; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.IntStream; /** * A packet of data that is writable or readable. * @author Calclavia */ public interface Packet { /** * @return Gets the ID of this packet, used to determine what this packet is about. */ int getID(); /** * Sets the ID of this packet, allowing it to be sent accordingly. * @return The packet itself. */ Packet setID(int id); /** * The player sending the packet */ Player player(); /** * Writes an arbitrary object, automatically finding the relevant class. * @param data Object to write * @return This packet */ default Packet write(Object data) { if (data instanceof Boolean) { writeBoolean((boolean) data); } else if (data instanceof Byte) { writeByte((byte) data); } else if (data instanceof Short) { writeShort((short) data); } else if (data instanceof Integer) { writeInt((int) data); } else if (data instanceof Long) { writeLong((long) data); } else if (data instanceof Character) { writeChar((Character) data); } else if (data instanceof Float) { writeFloat((float) data); } else if (data instanceof Double) { writeDouble((double) data); } else if (data instanceof String) { writeString((String) data); } else if (data instanceof Enum) { writeEnum((Enum) data); } else if (data instanceof Optional) { writeOptional((Optional) data); } else if (data instanceof Data) { writeData((Data) data); } else if (data instanceof Syncable) { ((Syncable) data).write(this); } else if (data instanceof Storable) { writeStorable((Storable) data); } else if (data instanceof Collection) { writeCollection((Collection) data); } else if (data instanceof Collection) { writeCollection((Collection) data); } else if (data instanceof Vector3D) { writeDouble(((Vector3D) data).getX()); writeDouble(((Vector3D) data).getY()); writeDouble(((Vector3D) data).getZ()); } else if (data instanceof Vector2D) { writeDouble(((Vector2D) data).getX()); writeDouble(((Vector2D) data).getY()); } else { throw new IllegalArgumentException("Packet attempt to write an invalid object: " + data); } return this; } default Packet $less$less$less(Object data) { return write(data); } /** * Sets the specified boolean at the current {@code writerIndex} * and increases the {@code writerIndex} by {@code 1} in this buffer. * @param value Data to write * @return This packet * @throws IndexOutOfBoundsException if {@code this.writableBytes} is less than {@code 1} */ Packet writeBoolean(boolean value); /** * Sets the specified byte at the current {@code writerIndex} * and increases the {@code writerIndex} by {@code 1} in this buffer. * The 24 high-order bits of the specified value are ignored. * @param value Data to write * @return This packet * @throws IndexOutOfBoundsException if {@code this.writableBytes} is less than {@code 1} */ Packet writeByte(int value); /** * Sets the specified 16-bit short integer at the current * {@code writerIndex} and increases the {@code writerIndex} by {@code 2} * in this buffer. The 16 high-order bits of the specified value are ignored. * @param value Data to write * @return This packet * @throws IndexOutOfBoundsException if {@code this.writableBytes} is less than {@code 2} */ Packet writeShort(int value); /** * Sets the specified 32-bit integer at the current {@code writerIndex} * and increases the {@code writerIndex} by {@code 4} in this buffer. * @param value Data to write * @return This packet * @throws IndexOutOfBoundsException if {@code this.writableBytes} is less than {@code 4} */ Packet writeInt(int value); /** * Sets the specified 64-bit long integer at the current * {@code writerIndex} and increases the {@code writerIndex} by {@code 8} * in this buffer. * @param value Data to write * @return This packet * @throws IndexOutOfBoundsException if {@code this.writableBytes} is less than {@code 8} */ Packet writeLong(long value); /** * Sets the specified 2-byte UTF-16 character at the current * {@code writerIndex} and increases the {@code writerIndex} by {@code 2} * in this buffer. The 16 high-order bits of the specified value are ignored. * @param value Data to write * @return This packet * @throws IndexOutOfBoundsException if {@code this.writableBytes} is less than {@code 2} */ Packet writeChar(int value); /** * Sets the specified 32-bit floating point number at the current * {@code writerIndex} and increases the {@code writerIndex} by {@code 4} * in this buffer. * @param value Data to write * @return This packet * @throws IndexOutOfBoundsException if {@code this.writableBytes} is less than {@code 4} */ Packet writeFloat(float value); /** * Sets the specified 64-bit floating point number at the current * {@code writerIndex} and increases the {@code writerIndex} by {@code 8} * in this buffer. * @param value Data to write * @return This packet * @throws IndexOutOfBoundsException if {@code this.writableBytes} is less than {@code 8} */ Packet writeDouble(double value); Packet writeString(String value); //TODO: Packet handler is bad at reading/writing enums for unknown reasons default Packet writeEnum(Enum data) { writeString(data.getClass().getName()); writeString(data.name()); return this; } default int getType(Class<?> compare) { return IntStream .range(0, Data.dataTypes.length) .filter(i -> Data.dataTypes[i].isAssignableFrom(compare)) .findFirst() .getAsInt(); } default Packet writeData(Data data) { //Write the data size writeInt(data.size()); //Write the data class writeString(data.className); data.forEach((k, v) -> { int typeID = getType(v.getClass()); //Write key writeString(k); //Write data type writeShort(typeID); //Write value write(v); } ); return this; } default Packet writeStorable(Storable storable) { writeData(Data.serialize(storable)); return this; } default Packet writeCollection(Collection col) { writeInt(col.size()); col.forEach(obj -> { writeShort(getType(obj.getClass())); write(obj); }); return this; } default Packet writeOptional(Optional optional) { if (optional.isPresent()) { writeShort(getType(optional.get().getClass())); write(optional.get()); } else { writeShort(-1); } return this; } Packet writeBytes(byte[] array); byte[] readBytes(int length); /** * Gets a boolean at the current {@code readerIndex} and increases * the {@code readerIndex} by {@code 1} in this buffer. * @return Data read from this packet * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 1} */ boolean readBoolean(); /** * Gets a byte at the current {@code readerIndex} and increases * the {@code readerIndex} by {@code 1} in this buffer. * @return Data read from this packet * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 1} */ byte readByte(); /** * Gets an unsigned byte at the current {@code readerIndex} and increases * the {@code readerIndex} by {@code 1} in this buffer. * @return Data read from this packet * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 1} */ short readUnsignedByte(); /** * Gets a 16-bit short integer at the current {@code readerIndex} * and increases the {@code readerIndex} by {@code 2} in this buffer. * @return Data read from this packet * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 2} */ short readShort(); /** * Gets a 32-bit integer at the current {@code readerIndex} * and increases the {@code readerIndex} by {@code 4} in this buffer. * @return Data read from this packet * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 4} */ int readInt(); /** * Gets an unsigned 32-bit integer at the current {@code readerIndex} * and increases the {@code readerIndex} by {@code 4} in this buffer. * @return Data read from this packet * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 4} */ long readUnsignedInt(); /** * Gets a 64-bit integer at the current {@code readerIndex} * and increases the {@code readerIndex} by {@code 8} in this buffer. * @return Data read from this packet * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 8} */ long readLong(); /** * Gets a 2-byte UTF-16 character at the current {@code readerIndex} * and increases the {@code readerIndex} by {@code 2} in this buffer. * @return Data read from this packet * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 2} */ char readChar(); /** * Gets a 32-bit floating point number at the current {@code readerIndex} * and increases the {@code readerIndex} by {@code 4} in this buffer. * @return Data read from this packet * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 4} */ float readFloat(); /** * Gets a 64-bit floating point number at the current {@code readerIndex} * and increases the {@code readerIndex} by {@code 8} in this buffer. * @return Data read from this packet * @throws IndexOutOfBoundsException if {@code this.readableBytes} is less than {@code 8} */ double readDouble(); String readString(); default Enum readEnum() { try { String enumClassName = readString(); Class<? extends Enum> className = (Class) Class.forName(enumClassName); return readEnum(className); } catch (Exception e) { throw new NetworkException("Failed to read enum.", e); } } default Enum readEnum(Class<? extends Enum> type) { return Enum.valueOf(type, readString()); } default Syncable readPacketHandler(Syncable handler) { handler.read(this); return handler; } /** * Reads a {@link Data} type. */ default Data readData() { Data readData = new Data(); int size = readInt(); readData.className = readString(); IntStream .range(0, size) .forEach(i -> { String key = readString(); short type = readShort(); Object value = read(Data.dataTypes[type]); readData.put(key, value); }); return readData; } default Object readStorable() { Data data = readData(); return Data.unserialize(data); } default <T> List<T> readList() { ArrayList arrayList = new ArrayList(); int size = readInt(); IntStream .range(0, size) .forEach(i -> { short type = readShort(); T value = (T) read(Data.dataTypes[type]); arrayList.add(value); }); return arrayList; } default <T> Set<T> readSet() { Set<T> set = new HashSet<>(); int size = readInt(); IntStream .range(0, size) .forEach(i -> { short type = readShort(); T value = (T) read(Data.dataTypes[type]); set.add(value); }); return set; } default <T> Optional<T> readOptional() { short type = readShort(); if (type != -1) { return (Optional<T>) Optional.of(read(Data.dataTypes[type])); } else { return Optional.empty(); } } default Vector2D readVector2D() { return new Vector2D(readDouble(), readDouble()); } default Vector3D readVector3D() { return new Vector3D(readDouble(), readDouble(), readDouble()); } default <T> T read(Class<T> clazz) { if (clazz == Boolean.class || clazz == boolean.class) { return (T) Boolean.valueOf(readBoolean()); } else if (clazz == Byte.class || clazz == byte.class) { return (T) Byte.valueOf(readByte()); } else if (clazz == Short.class || clazz == short.class) { return (T) Short.valueOf(readShort()); } else if (clazz == Integer.class || clazz == int.class) { return (T) Integer.valueOf(readInt()); } else if (clazz == Long.class || clazz == long.class) { return (T) Long.valueOf(readLong()); } else if (clazz == Character.class || clazz == char.class) { return (T) Character.valueOf(readChar()); } else if (clazz == Float.class || clazz == float.class) { return (T) Float.valueOf(readFloat()); } else if (clazz == Double.class || clazz == double.class) { return (T) Double.valueOf(readDouble()); } else if (clazz == String.class) { return (T) readString(); } else if (clazz == Optional.class) { return (T) readOptional(); } //Special data types that all convert into Data. else if (Syncable.class.isAssignableFrom(clazz)) { throw new NetworkException("Attempt to read PacketHandler object by its class"); } else if (Enum.class.isAssignableFrom(clazz)) { return (T) readEnum(); } else if (Data.class.isAssignableFrom(clazz)) { return (T) readData(); } else if (Vector3D.class.isAssignableFrom(clazz)) { return (T) readVector3D(); } else if (Vector2D.class.isAssignableFrom(clazz)) { return (T) readVector2D(); } else if (List.class.isAssignableFrom(clazz)) { return (T) readList(); } else if (Set.class.isAssignableFrom(clazz)) { return (T) readSet(); } else { return (T) readStorable(); } } }