001/*
002 * Units of Measurement Reference Implementation
003 * Copyright (c) 2005-2020, Units of Measurement project.
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 range of values.
041 * <p>
042 * Range limits MUST be presented in the same scale and have the same unit as measured data values.<br>
043 * Subclasses of QuantityRange should be immutable.
044 * 
045 * @param <Q>
046 *          The value of the range.
047 * 
048 * @author <a href="mailto:werner@units.tech">Werner Keil</a>
049 * @version 0.6, June 5, 2018
050 * @see <a href= "http://www.botts-inc.com/SensorML_1.0.1/schemaBrowser/SensorML_QuantityRange.html"> SensorML: QuantityRange</a>
051 */
052public class QuantityRange<Q extends Quantity<Q>> extends Range<Quantity<Q>> {
053
054  protected QuantityRange(Quantity<Q> min, Quantity<Q> max, Quantity<Q> resolution) {
055    super(min, max, resolution);
056  }
057
058  protected QuantityRange(Quantity<Q> min, Quantity<Q> max) {
059    super(min, max);
060  }
061
062  /**
063   * Returns an {@code QuantityRange} with the specified values.
064   *
065   * @param minimum
066   *          The minimum value for the quantity range.
067   * @param maximum
068   *          The maximum value for the quantity range.
069   * @param resolution
070   *          The resolution of the quantity range.
071   * @return an {@code QuantityRange} with the given values
072   */
073  @SuppressWarnings({ "rawtypes", "unchecked" })
074  public static QuantityRange of(Quantity minimum, Quantity maximum, Quantity resolution) {
075    if (!isCompatibleQuantityTriple(minimum, maximum, resolution)) {
076      throw new IllegalArgumentException();
077    }
078    return new QuantityRange(minimum, maximum, resolution);
079  }
080
081  /**
082   * Returns an {@code QuantityRange} with the specified values.
083   *
084   * @param minimum
085   *          The minimum value for the quantity range.
086   * @param maximum
087   *          The maximum value for the quantity range.
088   * @return a {@code QuantityRange} with the given values
089   */
090  @SuppressWarnings({ "rawtypes", "unchecked" })
091  public static QuantityRange of(Quantity minimum, Quantity maximum) {
092    if (!isCompatibleQuantityPair(minimum, maximum)) {
093      throw new IllegalArgumentException();
094    }
095    return new QuantityRange(minimum, maximum);
096  }
097
098  @SuppressWarnings({ "rawtypes", "unchecked" })
099  private static boolean isCompatibleQuantityPair(final Quantity q1, final Quantity q2) {
100    return q1 == null || q2 == null || q1.getUnit().isCompatible(q2.getUnit());
101  }
102
103  @SuppressWarnings("rawtypes")
104  private static boolean isCompatibleQuantityTriple(final Quantity q1, final Quantity q2, final Quantity q3) {
105    return isCompatibleQuantityPair(q1, q2) && isCompatibleQuantityPair(q1, q3) && isCompatibleQuantityPair(q2, q3);
106  }
107
108  private boolean isAboveMinimum(final Quantity<Q> q) {
109    if (q instanceof ComparableQuantity) return ((ComparableQuantity<Q>) q).isGreaterThanOrEqualTo(getMinimum());
110
111    final Quantity<Q> qConverted = q.to(getMinimum().getUnit());
112    return qConverted.getValue().doubleValue() >= getMinimum().getValue().doubleValue();
113  }
114
115  private boolean isBelowMaximum(final Quantity<Q> q) {
116    if (q instanceof ComparableQuantity) return ((ComparableQuantity<Q>) q).isLessThanOrEqualTo(getMaximum());
117
118    final Quantity<Q> qConverted = q.to(getMaximum().getUnit());
119    return qConverted.getValue().doubleValue() <= getMaximum().getValue().doubleValue();
120  }
121
122  private boolean fulfillsMaximumConstraint(final Quantity<Q> q) {
123    return !hasMaximum() || isBelowMaximum(q);
124  }
125
126  private boolean fulfillsMinimumConstraint(final Quantity<Q> q) {
127    return !hasMinimum() || isAboveMinimum(q);
128  }
129
130  @Override
131  public boolean contains(final Quantity<Q> q) {
132    if (q == null) {
133      throw new NullPointerException();
134    }
135    return q.getValue() != null && q.getUnit() != null && fulfillsMinimumConstraint(q) && fulfillsMaximumConstraint(q);
136  }
137
138  @Override
139  public boolean equals(final Object obj) {
140    if (this == obj) {
141      return true;
142    }
143    if (obj instanceof QuantityRange<?>) {
144      @SuppressWarnings("unchecked")
145      final QuantityRange<Q> other = (QuantityRange<Q>) obj;
146      return Objects.equals(getMinimum(), other.getMinimum()) && Objects.equals(getMaximum(), other.getMaximum())
147          && Objects.equals(getResolution(), other.getResolution());
148    }
149    return false;
150  }
151
152  @Override
153  public int hashCode() {
154    return Objects.hash(getMinimum(), getMaximum(), getResolution());
155  }
156
157  @Override
158  public String toString() {
159    final StringBuilder sb = new StringBuilder().append("min= ").append(getMinimum()).append(", max= ").append(getMaximum());
160    if (getResolution() != null) {
161      sb.append(", res= ").append(getResolution());
162    }
163    return sb.toString();
164  }
165}