diff --git a/src/com/projectswg/common/data/Pair.java b/src/com/projectswg/common/data/Pair.java new file mode 100644 index 0000000..3bb362b --- /dev/null +++ b/src/com/projectswg/common/data/Pair.java @@ -0,0 +1,192 @@ +package com.projectswg.common.data; + +import java.util.Objects; + +import com.projectswg.common.debug.Assert; +import com.projectswg.common.debug.Log; +import com.projectswg.common.encoding.CachedEncode; +import com.projectswg.common.encoding.Encodable; +import com.projectswg.common.encoding.Encoder; +import com.projectswg.common.encoding.StringType; +import com.projectswg.common.network.NetBuffer; +import com.projectswg.common.network.NetBufferStream; +import com.projectswg.common.persistable.Persistable; + +public class Pair implements Encodable, Persistable { + + private final StringType leftType; + private final StringType rightType; + private final Class leftClass; + private final Class rightClass; + private final CachedEncode cache; + + private T left; + private S right; + + private Pair(T left, S right, Class leftClass, Class rightClass, StringType leftType, StringType rightType) { + // Final checks - has to be either one or the other. These ensure other assumptions in the class will succeed + Assert.test((leftType == null && !(left instanceof String) && !leftClass.equals(String.class)) ^ (leftType != null && (left instanceof String) && leftClass.equals(String.class))); + Assert.test((rightType == null && !(right instanceof String) && !rightClass.equals(String.class)) ^ (rightType != null && (right instanceof String) && rightClass.equals(String.class))); + this.leftClass = leftClass; + this.rightClass = rightClass; + this.leftType = leftType; + this.rightType = rightType; + this.cache = new CachedEncode(this::encodeImpl); + setLeft(left); + setRight(right); + } + + public T getLeft() { + return left; + } + + public S getRight() { + return right; + } + + public void setLeft(T val1) { + this.left = val1; + cache.clearCached(); + } + + public void setRight(S val2) { + this.right = val2; + cache.clearCached(); + } + + @Override + public void decode(NetBuffer data) { + setLeft(smartDecode(data, leftClass, leftType)); + setRight(smartDecode(data, rightClass, rightType)); + } + + @Override + public byte[] encode() { + Objects.requireNonNull(left, "left is null in encode()"); + Objects.requireNonNull(right, "right is null in encode()"); + return cache.encode(); + } + + @Override + public int getLength() { + return cache.encode().length; + } + + @Override + public void save(NetBufferStream stream) { + smartSave(stream, left, leftType); + smartSave(stream, right, rightType); + } + + @Override + public void read(NetBufferStream stream) { + smartRead(stream, left, leftClass, leftType); + smartRead(stream, right, rightClass, rightType); + } + + private byte [] encodeImpl() { + if (left == null || right == null) + return new byte[0]; + + byte [] left = smartEncode(this.left, leftType); + byte [] right = smartEncode(this.right, rightType); + byte [] combined = new byte[left.length + right.length]; + System.arraycopy(left, 0, combined, 0, left.length); + System.arraycopy(right, 0, combined, left.length, right.length); + return combined; + } + + @SuppressWarnings("unchecked") // should succeed based on checks in constructor + private static U smartDecode(NetBuffer data, Class klass, StringType strType) { + if (strType != null) + return (U) data.getString(strType); + return (U) data.getGeneric(klass); + } + + private static byte [] smartEncode(Object obj, StringType strType) { + if (strType != null) + return Encoder.encode(obj, strType); + return Encoder.encode(obj); + } + + @SuppressWarnings("unchecked") // should succeed based on checks in constructor + private static U smartRead(NetBufferStream data, U obj, Class klass, StringType strType) { + if (strType != null) + return (U) data.getString(strType); + + // Try making our own persistable + if (obj == null && Persistable.class.isAssignableFrom(klass)) { + try { + obj = klass.newInstance(); + } catch (Exception e) { + Log.e(e); + } + } + + // If we succeeded, read it - if not, try grabbing some generic + if (obj instanceof Persistable) { + ((Persistable) obj).read(data); + } else { + obj = (U) data.getGeneric(klass); + } + return obj; + } + + private static void smartSave(NetBufferStream data, Object obj, StringType strType) { + if (strType != null) { + data.addString((String) obj, strType); + } else if (obj instanceof Persistable) { + ((Persistable) obj).save(data); + } else { + data.write(Encoder.encode(obj)); + } + } + + @SuppressWarnings("unchecked") // it's pretty obvious T.getClass() should be Class + public static Pair createPair(T left, S right) { + Objects.requireNonNull(left, "Left cannot be null in this method! Instead call createPair(left, right, leftClass, rightClass)"); + Objects.requireNonNull(right, "Right cannot be null in this method! Instead call createPair(left, right, leftClass, rightClass)"); + return createPair(left, right, (Class) left.getClass(), (Class) right.getClass()); + } + + public static Pair createPair(T left, S right, Class leftClass, Class rightClass) { + Assert.test(!(left instanceof String)); + Assert.test(!(right instanceof String)); + return new Pair<>(left, right, leftClass, rightClass, null, null); + } + + @SuppressWarnings("unchecked") // it's pretty obvious T.getClass() should be Class + public static Pair createPair(String left, T right, StringType type) { + Objects.requireNonNull(right, "Right cannot be null in this method! Instead call createPair(left, right, type, rightClass)"); + return createPair(left, right, type, (Class) right.getClass()); + } + + public static Pair createPair(String left, T right, StringType type, Class rightClass) { + Assert.test(!(right instanceof String)); + return new Pair<>(left, right, String.class, rightClass, type, null); + } + + @SuppressWarnings("unchecked") // it's pretty obvious T.getClass() should be Class + public static Pair createPair(T left, String right, StringType type) { + Objects.requireNonNull(left, "Left cannot be null in this method! Instead call createPair(left, right, leftClass, type)"); + return createPair(left, right, (Class) left.getClass(), type); + } + + public static Pair createPair(T left, String right, Class leftClass, StringType type) { + Assert.test(!(left instanceof String)); + return new Pair<>(left, right, leftClass, String.class, null, type); + } + + /** + * Creates a pair with two strings + * @param left the left string, can be null + * @param right the right string, can be null + * @param leftType the type of the left string: ASCII/UNICODE + * @param rightType the type of the right string: ASCII/UNICODE + * @return the corresponding pair + */ + public static Pair createPair(String left, String right, StringType leftType, StringType rightType) { + return new Pair<>(left, right, String.class, String.class, leftType, rightType); + } + +}