001/*
002 * Units of Measurement Reference Implementation
003 * Copyright (c) 2005-2024, Jean-Marie Dautelle, Werner Keil, Otavio Santana.
004 *
005 * All rights reserved.
006 *
007 * Redistribution and use in source and binary forms, with or without modification,
008 * are permitted provided that the following conditions are met:
009 *
010 * 1. Redistributions of source code must retain the above copyright notice,
011 *    this list of conditions and the following disclaimer.
012 *
013 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
014 *    and the following disclaimer in the documentation and/or other materials provided with the distribution.
015 *
016 * 3. Neither the name of JSR-385, Indriya nor the names of their contributors may be used to endorse or promote products
017 *    derived from this software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
020 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
021 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
022 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
023 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
026 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package tech.units.indriya.quantity;
031
032import java.util.Objects;
033
034import javax.measure.Quantity;
035
036import tech.units.indriya.ComparableQuantity;
037import tech.units.indriya.spi.Range;
038
039/**
040 * A Quantity Range is a pair of {@link Quantity} items that represent a {@link Range}
041 * of values.
042 * <p>
043 * Range limits MUST be presented in the same scale and have the same unit as
044 * measured data values.<br>
045 * Subclasses of QuantityRange should be <code>final</code> and immutable.
046 * 
047 * @param <Q> The value of the range.
048 * 
049 * @author <a href="mailto:werner@units.tech">Werner Keil</a>
050 * @version 2.1, Dec 7, 2023
051 * @see <a href=
052 *      "http://www.botts-inc.com/SensorML_1.0.1/schemaBrowser/SensorML_QuantityRange.html">
053 *      SensorML: QuantityRange</a>
054 */
055public class QuantityRange<Q extends Quantity<Q>> implements Range<Quantity<Q>> {
056        private final Quantity<Q> min;
057        private final Quantity<Q> max;
058        private Quantity<Q> res;
059
060        /**
061         * Construct an instance of QuantityRange with a min, max and res value.
062         *
063         * @param minimum    The minimum value for the range.
064         * @param maximum    The maximum value for the range.
065         * @param resolution The resolution of the range.
066         */
067        protected QuantityRange(Quantity<Q> minimum, Quantity<Q> maximum, Quantity<Q> resolution) {
068                this.min = minimum;
069                this.max = maximum;
070                this.res = resolution;
071        }
072
073        protected QuantityRange(Quantity<Q> min, Quantity<Q> max) {
074                this(min, max, null);
075        }
076
077        /**
078         * Returns an {@code QuantityRange} with the specified values.
079         *
080         * @param minimum    The minimum value for the quantity range.
081         * @param maximum    The maximum value for the quantity range.
082         * @param resolution The resolution of the quantity range.
083         * @return an {@code QuantityRange} with the given values
084         */
085        @SuppressWarnings({ "rawtypes", "unchecked" })
086        public static QuantityRange of(Quantity minimum, Quantity maximum, Quantity resolution) {
087                if (!isCompatibleQuantityTriple(minimum, maximum, resolution)) {
088                        throw new IllegalArgumentException();
089                }
090                return new QuantityRange(minimum, maximum, resolution);
091        }
092
093        /**
094         * Returns an {@code QuantityRange} with the specified values.
095         *
096         * @param minimum The minimum value for the quantity range.
097         * @param maximum The maximum value for the quantity range.
098         * @return a {@code QuantityRange} with the given values
099         */
100        @SuppressWarnings({ "rawtypes", "unchecked" })
101        public static QuantityRange of(Quantity minimum, Quantity maximum) {
102                if (!isCompatibleQuantityPair(minimum, maximum)) {
103                        throw new IllegalArgumentException();
104                }
105                return new QuantityRange(minimum, maximum);
106        }
107
108        @SuppressWarnings({ "rawtypes", "unchecked" })
109        private static boolean isCompatibleQuantityPair(final Quantity q1, final Quantity q2) {
110                return q1 == null || q2 == null || q1.getUnit().isCompatible(q2.getUnit());
111        }
112
113        @SuppressWarnings("rawtypes")
114        private static boolean isCompatibleQuantityTriple(final Quantity q1, final Quantity q2, final Quantity q3) {
115                return isCompatibleQuantityPair(q1, q2) && isCompatibleQuantityPair(q1, q3) && isCompatibleQuantityPair(q2, q3);
116        }
117
118        private boolean isAboveMinimum(final Quantity<Q> q) {
119                if (q instanceof ComparableQuantity)
120                        return ((ComparableQuantity<Q>) q).isGreaterThanOrEqualTo(getMinimum());
121
122                final Quantity<Q> qConverted = q.to(getMinimum().getUnit());
123                return qConverted.getValue().doubleValue() >= getMinimum().getValue().doubleValue();
124        }
125
126        private boolean isBelowMaximum(final Quantity<Q> q) {
127                if (q instanceof ComparableQuantity)
128                        return ((ComparableQuantity<Q>) q).isLessThanOrEqualTo(getMaximum());
129
130                final Quantity<Q> qConverted = q.to(getMaximum().getUnit());
131                return qConverted.getValue().doubleValue() <= getMaximum().getValue().doubleValue();
132        }
133
134        private boolean fulfillsMaximumConstraint(final Quantity<Q> q) {
135                return !hasMaximum() || isBelowMaximum(q);
136        }
137
138        private boolean fulfillsMinimumConstraint(final Quantity<Q> q) {
139                return !hasMinimum() || isAboveMinimum(q);
140        }
141
142        @Override
143        public boolean contains(final Quantity<Q> q) {
144                Objects.requireNonNull(q);
145                return q.getValue() != null && q.getUnit() != null && fulfillsMinimumConstraint(q)
146                                && fulfillsMaximumConstraint(q);
147        }
148
149        @Override
150        public boolean equals(final Object obj) {
151                if (this == obj) {
152                        return true;
153                }
154                if (obj instanceof QuantityRange<?>) {
155                        @SuppressWarnings("unchecked")
156                        final QuantityRange<Q> other = (QuantityRange<Q>) obj;
157                        return Objects.equals(getMinimum(), other.getMinimum()) && Objects.equals(getMaximum(), other.getMaximum())
158                                        && Objects.equals(getResolution(), other.getResolution());
159                }
160                return false;
161        }
162
163        @Override
164        public int hashCode() {
165                return Objects.hash(getMinimum(), getMaximum(), getResolution());
166        }
167
168        @Override
169        public String toString() {
170                final StringBuilder sb = new StringBuilder().append("min=").append(getMinimum()).append(", max=")
171                                .append(getMaximum());
172                if (getResolution() != null) {
173                        sb.append(", res=").append(getResolution());
174                }
175                return sb.toString();
176        }
177
178        @Override
179        public Quantity<Q> getMinimum() {
180                return min;
181        }
182
183        @Override
184        public Quantity<Q> getMaximum() {
185                return max;
186        }
187
188        @Override
189        public Quantity<Q> getResolution() {
190                return res;
191        }
192
193        @Override
194        public boolean hasMinimum() {
195                return min != null;
196        }
197
198        @Override
199        public boolean hasMaximum() {
200                return max != null;
201        }
202}