From a8e97eff956ddb8dd6d876925e26f672db6d7d39 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Tue, 1 Mar 2022 14:53:44 +0100 Subject: [PATCH] [jOOQ/jOOQ#2968] Add support for int4range --- .../java/org/jooq/meta/AbstractDatabase.java | 13 ++ .../bindings/AbstractRangeBinding.java | 79 ++++++++++++ .../bindings/IntegerRangeArrayBinding.java | 62 +++++++++ .../bindings/IntegerRangeBinding.java | 62 +++++++++ .../converters/AbstractRangeConverter.java | 93 ++++++++++++++ .../converters/IntegerRangeConverter.java | 69 ++++++++++ .../types/AbstractDiscreteRange.java | 118 ++++++++++++++++++ .../extensions/types/AbstractRange.java | 108 ++++++++++++++++ .../extensions/types/IntegerRange.java | 80 ++++++++++++ .../extensions/test/IntegerRangeTest.java | 79 ++++++++++++ 10 files changed, 763 insertions(+) create mode 100644 jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/bindings/AbstractRangeBinding.java create mode 100644 jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/bindings/IntegerRangeArrayBinding.java create mode 100644 jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/bindings/IntegerRangeBinding.java create mode 100644 jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/converters/AbstractRangeConverter.java create mode 100644 jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/converters/IntegerRangeConverter.java create mode 100644 jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/types/AbstractDiscreteRange.java create mode 100644 jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/types/AbstractRange.java create mode 100644 jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/types/IntegerRange.java create mode 100644 jOOQ-postgres-extensions/src/test/java/org/jooq/postgres/extensions/test/IntegerRangeTest.java diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/AbstractDatabase.java b/jOOQ-meta/src/main/java/org/jooq/meta/AbstractDatabase.java index a06ed00498..fdaa979917 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/AbstractDatabase.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/AbstractDatabase.java @@ -1529,6 +1529,19 @@ public abstract class AbstractDatabase implements Database { .withIncludeTypes("_cidr") .withPriority(Integer.MIN_VALUE) ); + + getConfiguredForcedTypes().add(new ForcedType() + .withUserType("org.jooq.postgres.extensions.types.IntegerRange") + .withBinding("org.jooq.postgres.extensions.bindings.IntegerRangeBinding") + .withIncludeTypes("int4range") + .withPriority(Integer.MIN_VALUE) + ); + getConfiguredForcedTypes().add(new ForcedType() + .withUserType("org.jooq.postgres.extensions.types.IntegerRange[]") + .withBinding("org.jooq.postgres.extensions.bindings.IntegerRangeArrayBinding") + .withIncludeTypes("_int4range") + .withPriority(Integer.MIN_VALUE) + ); } catch (ClassNotFoundException ignore) { log.debug("Built in data types", "org.jooq.postgres.extensions.types.Hstore not found on classpath, ignoring built in data type extensions"); diff --git a/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/bindings/AbstractRangeBinding.java b/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/bindings/AbstractRangeBinding.java new file mode 100644 index 0000000000..4f64466d14 --- /dev/null +++ b/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/bindings/AbstractRangeBinding.java @@ -0,0 +1,79 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.postgres.extensions.bindings; + +import java.sql.SQLException; +import java.sql.Types; + +import org.jooq.BindingGetResultSetContext; +import org.jooq.BindingGetStatementContext; +import org.jooq.BindingRegisterContext; +import org.jooq.BindingSetStatementContext; +import org.jooq.postgres.extensions.types.AbstractRange; + +/** + * A binding for the PostgreSQL range data types. + * + * @author Lukas Eder + */ +abstract class AbstractRangeBinding> extends AbstractPostgresBinding { + + // TODO: This looks just like the AbstractInetBinding. Perhaps rename? + + @Override + public void register(final BindingRegisterContext ctx) throws SQLException { + ctx.statement().registerOutParameter(ctx.index(), Types.VARCHAR); + } + + @Override + public void set(final BindingSetStatementContext ctx) throws SQLException { + Object value = ctx.convert(converter()).value(); + + ctx.statement().setString(ctx.index(), value == null ? null : "" + value); + } + + @Override + public void get(final BindingGetResultSetContext ctx) throws SQLException { + ctx.convert(converter()).value(ctx.resultSet().getString(ctx.index())); + } + + @Override + public void get(final BindingGetStatementContext ctx) throws SQLException { + ctx.convert(converter()).value(ctx.statement().getString(ctx.index())); + } +} diff --git a/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/bindings/IntegerRangeArrayBinding.java b/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/bindings/IntegerRangeArrayBinding.java new file mode 100644 index 0000000000..927e9d3029 --- /dev/null +++ b/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/bindings/IntegerRangeArrayBinding.java @@ -0,0 +1,62 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.postgres.extensions.bindings; + +import org.jooq.Converter; +import org.jooq.postgres.extensions.converters.IntegerRangeConverter; +import org.jooq.postgres.extensions.types.IntegerRange; + +/** + * A binding for the PostgreSQL inet[] data type. + * + * @author Lukas Eder + */ +public class IntegerRangeArrayBinding extends AbstractPostgresArrayBinding { + + private static final Converter CONVERTER = new IntegerRangeConverter().forArrays(); + + @Override + public Converter converter() { + return CONVERTER; + } + + @Override + protected String castType() { + return "int4range[]"; + } +} diff --git a/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/bindings/IntegerRangeBinding.java b/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/bindings/IntegerRangeBinding.java new file mode 100644 index 0000000000..775d9a7243 --- /dev/null +++ b/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/bindings/IntegerRangeBinding.java @@ -0,0 +1,62 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.postgres.extensions.bindings; + +import org.jooq.Converter; +import org.jooq.postgres.extensions.converters.IntegerRangeConverter; +import org.jooq.postgres.extensions.types.IntegerRange; + +/** + * A binding for the PostgreSQL int4range data type. + * + * @author Lukas Eder + */ +public class IntegerRangeBinding extends AbstractRangeBinding { + + private static final Converter CONVERTER = new IntegerRangeConverter(); + + @Override + public Converter converter() { + return CONVERTER; + } + + @Override + protected String castType() { + return "int4range"; + } +} diff --git a/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/converters/AbstractRangeConverter.java b/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/converters/AbstractRangeConverter.java new file mode 100644 index 0000000000..651920a9e4 --- /dev/null +++ b/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/converters/AbstractRangeConverter.java @@ -0,0 +1,93 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.postgres.extensions.converters; + +import static org.jooq.tools.StringUtils.isBlank; + +import org.jooq.impl.AbstractConverter; +import org.jooq.postgres.extensions.types.AbstractRange; + +/** + * A converter for {@link AbstractRange} types. + * + * @author Lukas Eder + */ +abstract class AbstractRangeConverter> extends AbstractConverter { + + public AbstractRangeConverter(Class toType) { + super(Object.class, toType); + } + + abstract U construct(String lower, boolean lowerIncluding, String upper, boolean upperIncluding); + abstract U empty(); + + @Override + public U from(Object t) { + if (t == null) + return null; + + String s = t.toString(); + if ("empty".equals(s)) + return empty(); + + String[] a = s.split(","); + String s0 = a[0]; + String s1 = a[1]; + + String lower = s0.substring(1); + boolean lowerIncluding = s0.charAt(0) == '['; + int l = s1.length() - 1; + String upper = s1.substring(0, l); + boolean upperIncluding = s1.charAt(l) == ']'; + + return construct( + isBlank(lower) ? null : lower, + lowerIncluding, + isBlank(upper) ? null : upper, + upperIncluding + ); + } + + @Override + public Object to(U u) { + if (u == null) + return null; + else + return u.toString(); + } +} diff --git a/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/converters/IntegerRangeConverter.java b/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/converters/IntegerRangeConverter.java new file mode 100644 index 0000000000..61a8c3d5e1 --- /dev/null +++ b/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/converters/IntegerRangeConverter.java @@ -0,0 +1,69 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.postgres.extensions.converters; + +import static org.jooq.postgres.extensions.types.IntegerRange.integerRange; + +import org.jooq.postgres.extensions.types.IntegerRange; + +/** + * A converter for {@link IntegerRange}. + * + * @author Lukas Eder + */ +public class IntegerRangeConverter extends AbstractRangeConverter { + + public IntegerRangeConverter() { + super(IntegerRange.class); + } + + @Override + final IntegerRange construct(String lower, boolean lowerIncluding, String upper, boolean upperIncluding) { + return integerRange( + lower == null ? null : Integer.valueOf(lower), + lowerIncluding, + upper == null ? null : Integer.valueOf(upper), + upperIncluding + ); + } + + @Override + final IntegerRange empty() { + return integerRange(0, 0); + } +} diff --git a/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/types/AbstractDiscreteRange.java b/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/types/AbstractDiscreteRange.java new file mode 100644 index 0000000000..13aa145702 --- /dev/null +++ b/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/types/AbstractDiscreteRange.java @@ -0,0 +1,118 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.postgres.extensions.types; + +import java.util.Objects; + +/** + * A data type representing the PostgreSQL range type for discrete ranges. + * + * @author Lukas Eder + */ +abstract class AbstractDiscreteRange> extends AbstractRange { + + AbstractDiscreteRange(T lower, boolean lowerIncluding, T upper, boolean upperIncluding) { + super(lower, lowerIncluding, upper, upperIncluding); + } + + /** + * Given a value t, get the next value. + */ + abstract T next(T t); + + /** + * Given a value t, get the previous value. + */ + abstract T prev(T t); + + /** + * Construct a new instance of this type. + */ + abstract R construct(T lower, T upper); + + final boolean isCanonical() { + return lowerIncluding() && !upperIncluding(); + } + + @SuppressWarnings("unchecked") + final R canonical() { + if (isCanonical()) + return (R) this; + + T l = lower(); + T u = upper(); + + if (!lowerIncluding() && l != null) + l = next(l); + + // This can overflow for Integer and Long. In PostgreSQL, an overflow + // will cause an error. We might deal with this too, in the future + if (upperIncluding() && u != null) + u = next(u); + + return construct(l, u); + } + + @Override + public int hashCode() { + if (isCanonical()) + return Objects.hash(lower(), upper()); + else + return canonical().hashCode(); + } + + @SuppressWarnings("unchecked") + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + + R other = (R) obj; + boolean c1 = isCanonical(); + boolean c2 = other.isCanonical(); + + R r1 = c1 ? (R) this : canonical(); + R r2 = c2 ? other : other.canonical(); + + return Objects.equals(r1.lower(), r2.lower()) && Objects.equals(r1.upper(), r2.upper()); + } +} diff --git a/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/types/AbstractRange.java b/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/types/AbstractRange.java new file mode 100644 index 0000000000..35c5424001 --- /dev/null +++ b/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/types/AbstractRange.java @@ -0,0 +1,108 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.postgres.extensions.types; + +import java.io.Serializable; +import java.util.Objects; + +import org.jetbrains.annotations.Nullable; + +/** + * A data type representing the PostgreSQL range type. + * + * @author Lukas Eder + */ +public abstract class AbstractRange implements Serializable { + + private final T lower; + private final boolean lowerIncluding; + private final T upper; + private final boolean upperIncluding; + + AbstractRange(T lower, boolean lowerIncluding, T upper, boolean upperIncluding) { + this.lower = lower; + this.lowerIncluding = lowerIncluding; + this.upper = upper; + this.upperIncluding = upperIncluding; + } + + @Nullable + public final T lower() { + return lower; + } + + public final boolean lowerIncluding() { + return lowerIncluding; + } + + @Nullable + public final T upper() { + return upper; + } + + public final boolean upperIncluding() { + return upperIncluding; + } + + @Override + public int hashCode() { + return Objects.hash(lower, lowerIncluding, upper, upperIncluding); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AbstractRange other = (AbstractRange) obj; + return Objects.equals(lower, other.lower) && lowerIncluding == other.lowerIncluding + && Objects.equals(upper, other.upper) && upperIncluding == other.upperIncluding; + } + + @Override + public String toString() { + return (lowerIncluding ? "[" : "(") + + (lower == null ? "" : "" + lower) + + "," + + (upper == null ? "" : "" + upper) + + (upperIncluding ? "]" : ")"); + } +} diff --git a/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/types/IntegerRange.java b/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/types/IntegerRange.java new file mode 100644 index 0000000000..8641e43036 --- /dev/null +++ b/jOOQ-postgres-extensions/src/main/java/org/jooq/postgres/extensions/types/IntegerRange.java @@ -0,0 +1,80 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.postgres.extensions.types; + +/** + * A data type representing the PostgreSQL int4range type. + * + * @author Lukas Eder + */ +public final class IntegerRange extends AbstractDiscreteRange { + + private IntegerRange(Integer lower, boolean lowerIncluding, Integer upper, boolean upperIncluding) { + super(lower, lowerIncluding, upper, upperIncluding); + } + + /** + * Create a new {@link IntegerRange} with a inclusive lower bound and an + * exclusive upper bound. + */ + public static final IntegerRange integerRange(Integer lower, Integer upper) { + return new IntegerRange(lower, true, upper, false); + } + + /** + * Create a new {@link IntegerRange}. + */ + public static final IntegerRange integerRange(Integer lower, boolean lowerIncluding, Integer upper, boolean upperIncluding) { + return new IntegerRange(lower, lowerIncluding, upper, upperIncluding); + } + + @Override + final IntegerRange construct(Integer lower, Integer upper) { + return new IntegerRange(lower, true, upper, false); + } + + @Override + final Integer next(Integer t) { + return t.intValue() + 1; + } + + @Override + final Integer prev(Integer t) { + return t.intValue() - 1; + } +} diff --git a/jOOQ-postgres-extensions/src/test/java/org/jooq/postgres/extensions/test/IntegerRangeTest.java b/jOOQ-postgres-extensions/src/test/java/org/jooq/postgres/extensions/test/IntegerRangeTest.java new file mode 100644 index 0000000000..5d041d5a69 --- /dev/null +++ b/jOOQ-postgres-extensions/src/test/java/org/jooq/postgres/extensions/test/IntegerRangeTest.java @@ -0,0 +1,79 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.postgres.extensions.test; + +import static org.jooq.postgres.extensions.types.IntegerRange.integerRange; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import org.junit.Test; + +public class IntegerRangeTest { + + @Test + public void testEqualsHashCode() { + assertEqualsHashCode(integerRange(0, 0), integerRange(0, 0)); + assertEqualsHashCode(integerRange(0, 1), integerRange(0, 1)); + assertEqualsHashCode(integerRange(0, 2), integerRange(0, true, 1, true)); + assertEqualsHashCode(integerRange(0, 2), integerRange(0, true, 2, false)); + assertEqualsHashCode(integerRange(0, 2), integerRange(-1, false, 1, true)); + assertEqualsHashCode(integerRange(0, 2), integerRange(-1, false, 2, false)); + + assertEqualsHashCode(integerRange(0, null), integerRange(0, true, null, true)); + assertEqualsHashCode(integerRange(0, null), integerRange(0, true, null, false)); + assertEqualsHashCode(integerRange(0, null), integerRange(-1, false, null, true)); + assertEqualsHashCode(integerRange(0, null), integerRange(-1, false, null, false)); + + assertEqualsHashCode(integerRange(null, 2), integerRange(null, true, 1, true)); + assertEqualsHashCode(integerRange(null, 2), integerRange(null, true, 2, false)); + assertEqualsHashCode(integerRange(null, 2), integerRange(null, false, 1, true)); + assertEqualsHashCode(integerRange(null, 2), integerRange(null, false, 2, false)); + + assertEqualsHashCode(integerRange(null, null), integerRange(null, true, null, true)); + assertEqualsHashCode(integerRange(null, null), integerRange(null, true, null, false)); + assertEqualsHashCode(integerRange(null, null), integerRange(null, false, null, true)); + assertEqualsHashCode(integerRange(null, null), integerRange(null, false, null, false)); + + assertNotEquals(integerRange(0, 1), integerRange(1, 0)); + } + + private void assertEqualsHashCode(Object expected, Object actual) { + assertEquals(expected, actual); + assertEquals(expected.hashCode(), actual.hashCode()); + } +}