class WindowFrameDefinition implements Writeable {

    private final Mode mode;
    private final FrameBoundDefinition start;
    private final FrameBoundDefinition end;

    public WindowFrameDefinition(StreamInput in) throws IOException {
        mode = in.readEnum(Mode.class);
        start = new FrameBoundDefinition(in);
        end = in.readOptionalWriteable(FrameBoundDefinition::new);
    }

    public WindowFrameDefinition(Mode mode, FrameBoundDefinition start, @Nullable FrameBoundDefinition end) {
        this.mode = mode;
        this.start = start;
        if (end != null) {
            this.end = end;
        } else {
            this.end = new FrameBoundDefinition(FrameBound.Type.CURRENT_ROW, Literal.NULL);
        }
    }

    public Mode mode() {
        return mode;
    }

    public FrameBoundDefinition start() {
        return start;
    }

    public FrameBoundDefinition end() {
        return end;
    }

    public WindowFrameDefinition map(Function<? super Symbol, ? extends Symbol> mapper) {
        FrameBoundDefinition newStart = start.map(mapper);
        FrameBoundDefinition newEnd = end.map(mapper);
        if (newStart == start && newEnd == end) {
            return this;
        } else {
            return new WindowFrameDefinition(mode, newStart, newEnd);
        }
    }

    @Override
    public void writeTo(StreamOutput out) throws IOException {
        out.writeEnum(mode);
        start.writeTo(out);
        out.writeOptionalWriteable(end);
    }

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

    @Override
    public int hashCode() {
        return Objects.hash(mode, start, end);
    }

    @Override
    public String toString() {
        return "WindowFrame{" +
               "type=" + mode +
               ", start=" + start +
               ", end=" + end +
               '}';
    }
}

        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        WindowFrameDefinition that = (WindowFrameDefinition) o;
        return mode == that.mode &&
               Objects.equals(start, that.start) &&
               Objects.equals(end, that.end);
    
<no_super_class>