package openmods.network.rpc;
import com.google.common.base.Preconditions;
import com.google.common.reflect.TypeToken;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import openmods.serializable.SerializerRegistry;
import openmods.utils.AnnotationMap;
import openmods.utils.CachedFactory;
import openmods.utils.io.IStreamReader;
import openmods.utils.io.IStreamSerializer;
import openmods.utils.io.IStreamWriter;
public class MethodParamsCodec {
private static class MethodParam {
public final Type type;
public final boolean isNullable;
public final IStreamSerializer<Object> serializer;
public MethodParam(Type type, Annotation[] annotations) {
this.type = type;
AnnotationMap annotationsMap = new AnnotationMap(annotations);
this.isNullable = annotationsMap.hasAnnotation(NullableArg.class);
this.serializer = SerializerRegistry.instance.findSerializer(type);
Preconditions.checkNotNull(this.serializer, "Failed to find serializer for type %s", type);
}
public void validate() {
validate(TypeToken.of(type));
}
private void validate(TypeToken<?> type) {
Preconditions.checkState(!type.isPrimitive() || !isNullable, "Primitive types can't be nullable");
if (type.isArray()) validate(type.getComponentType());
}
@Override
public String toString() {
return "MethodParam [type=" + type + ", nullable=" + isNullable + "]";
}
}
private final Method method;
private final MethodParam[] params;
MethodParamsCodec(Method method) {
this.method = method;
Annotation[][] annotations = method.getParameterAnnotations();
Class<?>[] types = method.getParameterTypes();
this.params = new MethodParam[types.length];
for (int i = 0; i < params.length; i++)
this.params[i] = new MethodParam(types[i], annotations[i]);
}
public void writeArgs(DataOutput output, Object... args) {
if (args == null) {
Preconditions.checkArgument(0 == params.length,
"Argument list length mismatch, expected %d, got 0", params.length);
return;
}
Preconditions.checkArgument(args.length == params.length,
"Argument list length mismatch, expected %d, got %d", params.length, args.length);
for (int i = 0; i < args.length; i++) {
MethodParam param = params[i];
try {
writeArg(output, i, param.serializer, param.isNullable, args[i]);
} catch (Exception e) {
throw new RuntimeException(String.format("Failed to write argument %d from method %s", i, method), e);
}
}
}
private static void writeArg(DataOutput output, int argIndex, IStreamWriter<Object> writer, boolean isNullable, Object value) throws IOException {
if (isNullable) {
if (value == null) {
output.writeBoolean(false);
return;
}
output.writeBoolean(true);
} else {
Preconditions.checkNotNull(value, "Only @NullableArg arguments can be null");
}
writer.writeToStream(value, output);
}
public Object[] readArgs(DataInput input) {
if (params.length == 0) return null;
Object[] result = new Object[params.length];
for (int i = 0; i < params.length; i++) {
MethodParam param = params[i];
try {
result[i] = readArg(input, param.serializer, param.isNullable);
} catch (Exception e) {
throw new RuntimeException(String.format("Failed to read argument %d from method %s", i, method), e);
}
}
return result;
}
private static Object readArg(DataInput input, IStreamReader<Object> reader, boolean isNullable) throws IOException {
if (isNullable) {
boolean hasValue = input.readBoolean();
if (!hasValue) return null;
}
return reader.readFromStream(input);
}
public void validate() {
for (int i = 0; i < params.length; i++) {
try {
params[i].validate();
} catch (Exception e) {
throw new IllegalStateException(String.format("Failed to validate arg %d of method %s", i, method), e);
}
}
}
private static final CachedFactory<Method, MethodParamsCodec> INSTANCES = new CachedFactory<Method, MethodParamsCodec>() {
@Override
protected MethodParamsCodec create(Method key) {
return new MethodParamsCodec(key);
}
};
public static synchronized MethodParamsCodec create(Method method) {
return INSTANCES.getOrCreate(method);
}
}