class Aggregation implements Symbol {

    private static final long SHALLOW_SIZE = RamUsageEstimator.shallowSizeOfInstance(Aggregation.class);

    private final Signature signature;
    private final DataType<?> boundSignatureReturnType;
    private final List<Symbol> inputs;
    private final DataType<?> valueType;
    private final Symbol filter;

    public Aggregation(Signature signature, DataType<?> valueType, List<Symbol> inputs) {
        this(signature,
            valueType,
            valueType,
            inputs,
            Literal.BOOLEAN_TRUE
        );
    }

    public Aggregation(Signature signature,
                       DataType<?> boundSignatureReturnType,
                       DataType<?> valueType,
                       List<Symbol> inputs,
                       Symbol filter) {
        requireNonNull(inputs, "inputs must not be null");
        requireNonNull(filter, "filter must not be null");

        this.valueType = valueType;
        this.signature = signature;
        this.boundSignatureReturnType = boundSignatureReturnType;
        this.inputs = inputs;
        this.filter = filter;
    }

    public Aggregation(StreamInput in) throws IOException {
        Signature generatedSignature = null;
        if (in.getVersion().before(Version.V_5_0_0)) {
            generatedSignature = Signature.readFromFunctionInfo(in);
        }
        valueType = DataTypes.fromStream(in);
        if (in.getVersion().onOrAfter(Version.V_4_1_0)) {
            filter = Symbols.fromStream(in);
        } else {
            filter = Literal.BOOLEAN_TRUE;
        }
        inputs = Symbols.listFromStream(in);
        if (in.getVersion().onOrAfter(Version.V_4_2_0)) {
            if (in.getVersion().before(Version.V_5_0_0)) {
                in.readBoolean();
            }
            signature = new Signature(in);
            boundSignatureReturnType = DataTypes.fromStream(in);
        } else {
            assert generatedSignature != null : "expecting a non-null generated signature";
            signature = generatedSignature;
            boundSignatureReturnType = valueType;
        }
    }

    @Override
    public SymbolType symbolType() {
        return SymbolType.AGGREGATION;
    }

    @Override
    public <C, R> R accept(SymbolVisitor<C, R> visitor, C context) {
        return visitor.visitAggregation(this, context);
    }

    @Override
    public DataType<?> valueType() {
        return valueType;
    }

    public DataType<?> boundSignatureReturnType() {
        return boundSignatureReturnType;
    }

    @Nullable
    public Signature signature() {
        return signature;
    }

    public List<Symbol> inputs() {
        return inputs;
    }

    public Symbol filter() {
        return filter;
    }

    @Override
    public void writeTo(StreamOutput out) throws IOException {
        if (out.getVersion().before(Version.V_5_0_0)) {
            signature.writeAsFunctionInfo(out, Symbols.typeView(inputs));
        }
        DataTypes.toStream(valueType, out);
        if (out.getVersion().onOrAfter(Version.V_4_1_0)) {
            Symbols.toStream(filter, out);
        }
        Symbols.toStream(inputs, out);
        if (out.getVersion().onOrAfter(Version.V_4_2_0)) {
            if (out.getVersion().before(Version.V_5_0_0)) {
                out.writeBoolean(true);
            }
            signature.writeTo(out);
            DataTypes.toStream(boundSignatureReturnType, out);
        }
    }

    @Override
    public boolean equals(Object o) {<FILL_FUNCTION_BODY>}

    @Override
    public int hashCode() {
        return Objects.hash(signature, inputs, valueType, filter);
    }

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

    @Override
    public String toString(Style style) {
        StringBuilder sb = new StringBuilder(signature.getName().name())
            .append("(");
        for (int i = 0; i < inputs.size(); i++) {
            sb.append(inputs.get(i).toString(style));
            if (i + 1 < inputs.size()) {
                sb.append(", ");
            }
        }
        sb.append(")");
        return sb.toString();
    }

    @Override
    public long ramBytesUsed() {
        return SHALLOW_SIZE
            + signature.ramBytesUsed()
            + boundSignatureReturnType.ramBytesUsed()
            + inputs.stream().mapToLong(Symbol::ramBytesUsed).sum()
            + valueType.ramBytesUsed()
            + filter.ramBytesUsed();
    }
}

        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Aggregation that = (Aggregation) o;
        return Objects.equals(signature, that.signature) &&
               Objects.equals(inputs, that.inputs) &&
               Objects.equals(valueType, that.valueType) &&
               Objects.equals(filter, that.filter);
    
<no_super_class>