presto-main/src/main/java/io/prestosql/operator/aggregation/state/StateCompiler.java/StateCompiler/generateSerialize
class StateCompiler:    private static <T> void generateSerialize(ClassDefinition definition, CallSiteBinder binder, Class<T> clazz, List<StateField> fields)
    {
        Parameter state = arg("state", Object.class);
        Parameter out = arg("out", BlockBuilder.class);
        MethodDefinition method = definition.declareMethod(a(PUBLIC), "serialize", type(void.class), state, out);
        Scope scope = method.getScope();
        BytecodeBlock serializerBody = method.getBody();

        if (fields.size() == 0) {
            serializerBody.append(out.invoke("appendNull", BlockBuilder.class).pop());
        }
        else if (fields.size() == 1) {
            Method getter = getGetter(clazz, getOnlyElement(fields));
            SqlTypeBytecodeExpression sqlType = constantType(binder, getOnlyElement(fields).getSqlType());
            Variable fieldValue = scope.declareVariable(getter.getReturnType(), "value");
            serializerBody.append(fieldValue.set(state.cast(getter.getDeclaringClass()).invoke(getter)));
            if (!getOnlyElement(fields).isPrimitiveType()) {
                serializerBody.append(new IfStatement()
                        .condition(equal(fieldValue, constantNull(getter.getReturnType())))
                        .ifTrue(out.invoke("appendNull", BlockBuilder.class).pop())
                        .ifFalse(sqlType.writeValue(out, fieldValue)));
            }
            else {
                // For primitive type, we need to cast here because we serialize byte fields with TINYINT/INTEGER (whose java type is long).
                serializerBody.append(sqlType.writeValue(out, fieldValue.cast(getOnlyElement(fields).getSqlType().getJavaType())));
            }
        }
        else if (fields.size() > 1) {
            Variable rowBuilder = scope.declareVariable(BlockBuilder.class, "rowBuilder");
            serializerBody.append(rowBuilder.set(out.invoke("beginBlockEntry", BlockBuilder.class)));
            for (StateField field : fields) {
                Method getter = getGetter(clazz, field);
                SqlTypeBytecodeExpression sqlType = constantType(binder, field.getSqlType());
                Variable fieldValue = scope.createTempVariable(getter.getReturnType());
                serializerBody.append(fieldValue.set(state.cast(getter.getDeclaringClass()).invoke(getter)));
                if (!field.isPrimitiveType()) {
                    serializerBody.append(new IfStatement().condition(equal(fieldValue, constantNull(getter.getReturnType())))
                            .ifTrue(rowBuilder.invoke("appendNull", BlockBuilder.class).pop())
                            .ifFalse(sqlType.writeValue(rowBuilder, fieldValue)));
                }
                else {
                    // For primitive type, we need to cast here because we serialize byte fields with TINYINT/INTEGER (whose java type is long).
                    serializerBody.append(sqlType.writeValue(rowBuilder, fieldValue.cast(field.getSqlType().getJavaType())));
                }
            }
            serializerBody.append(out.invoke("closeEntry", BlockBuilder.class).pop());
        }
        serializerBody.ret();
    }