001// Licensed under the MIT license. See LICENSE file in the project root for full license information.
002
003package de.bytefish.pgbulkinsert.pgsql.model.range;
004
005import java.util.Objects;
006
007// https://github.com/npgsql/npgsql/blob/d4132d0d546594629bcef658bcb1418b4a8624cc/src/Npgsql/NpgsqlTypes/NpgsqlRange.cs
008public class Range<TElementType> {
009
010    private int flags;
011
012    private TElementType lowerBound;
013
014    private TElementType upperBound;
015
016    public Range(TElementType lowerBound, TElementType upperBound) {
017        this(lowerBound, true, false, upperBound, true, false);
018    }
019
020    public Range(TElementType lowerBound, boolean lowerBoundIsInclusive, TElementType upperBound, boolean upperBoundIsInclusive) {
021        this(lowerBound, lowerBoundIsInclusive, false, upperBound, upperBoundIsInclusive, false);
022    }
023
024    public Range(TElementType lowerBound, boolean lowerBoundIsInclusive, boolean lowerBoundInfinite,
025                 TElementType upperBound, boolean upperBoundIsInclusive, boolean upperBoundInfinite) {
026        this(lowerBound, upperBound, evaluateBoundaryFlags(lowerBoundIsInclusive, upperBoundIsInclusive, lowerBoundInfinite, upperBoundInfinite));
027    }
028
029
030    private Range(TElementType lowerBound, TElementType upperBound, int flags) {
031        this.lowerBound = (flags & RangeFlags.LowerBoundInfinite) != 0 ? null : lowerBound;
032        this.upperBound = (flags & RangeFlags.UpperBoundInfinite) != 0 ? null : upperBound;
033        this.flags = flags;
034
035        // TODO Check this!
036        if (lowerBound == null) {
037            this.flags |= RangeFlags.LowerBoundInfinite;
038        }
039
040        if (upperBound == null) {
041            this.flags |= RangeFlags.UpperBoundInfinite;
042        }
043
044        if (isEmptyRange(lowerBound, upperBound, flags)) {
045            this.lowerBound = null;
046            this.upperBound = null;
047            this.flags = RangeFlags.Empty;
048        }
049    }
050
051    private boolean isEmptyRange(TElementType lowerBound, TElementType upperBound, int flags) {
052        // ---------------------------------------------------------------------------------
053        // We only want to check for those conditions that are unambiguously erroneous:
054        //   1. The bounds must not be default values (including null).
055        //   2. The bounds must be definite (non-infinite).
056        //   3. The bounds must be inclusive.
057        //   4. The bounds must be considered equal.
058        //
059        // See:
060        //  - https://github.com/npgsql/npgsql/pull/1939
061        //  - https://github.com/npgsql/npgsql/issues/1943
062        // ---------------------------------------------------------------------------------
063
064        if ((flags & RangeFlags.Empty) == RangeFlags.Empty)
065            return true;
066
067        if ((flags & RangeFlags.Infinite) == RangeFlags.Infinite)
068            return false;
069
070        if ((flags & RangeFlags.Inclusive) == RangeFlags.Inclusive)
071            return false;
072
073        return Objects.equals(lowerBound, upperBound);
074    }
075
076
077    private static int evaluateBoundaryFlags(boolean lowerBoundIsInclusive, boolean upperBoundIsInclusive, boolean lowerBoundInfinite, boolean upperBoundInfinite) {
078
079        int result = RangeFlags.None;
080
081        // This is the only place flags are calculated.
082        if (lowerBoundIsInclusive)
083            result |= RangeFlags.LowerBoundInclusive;
084        if (upperBoundIsInclusive)
085            result |= RangeFlags.UpperBoundInclusive;
086        if (lowerBoundInfinite)
087            result |= RangeFlags.LowerBoundInfinite;
088        if (upperBoundInfinite)
089            result |= RangeFlags.UpperBoundInfinite;
090
091        // PostgreSQL automatically converts inclusive-infinities.
092        // See: https://www.postgresql.org/docs/current/static/rangetypes.html#RANGETYPES-INFINITE
093        if ((result & RangeFlags.LowerInclusiveInfinite) == RangeFlags.LowerInclusiveInfinite) {
094            result &= ~RangeFlags.LowerBoundInclusive;
095        }
096
097        if ((result & RangeFlags.UpperInclusiveInfinite) == RangeFlags.UpperInclusiveInfinite) {
098            result &= ~RangeFlags.UpperBoundInclusive;
099        }
100
101        return result;
102    }
103
104    public int getFlags() {
105        return flags;
106    }
107
108    public boolean isEmpty() {
109        return (flags & RangeFlags.Empty) != 0;
110    }
111
112    public boolean isLowerBoundInfinite() {
113        return (flags & RangeFlags.LowerBoundInfinite) != 0;
114    }
115
116    public boolean isUpperBoundInfinite() {
117        return (flags & RangeFlags.UpperBoundInfinite) != 0;
118    }
119
120    public TElementType getLowerBound() {
121        return lowerBound;
122    }
123
124    public void setLowerBound(TElementType lowerBound) {
125        this.lowerBound = lowerBound;
126    }
127
128    public TElementType getUpperBound() {
129        return upperBound;
130    }
131
132    public void setUpperBound(TElementType upperBound) {
133        this.upperBound = upperBound;
134    }
135}