001package org.clafer.ast;
002
003import java.io.Serializable;
004
005/**
006 * Low and high cardinality. Immutable.
007 *
008 * @author jimmy
009 */
010public class Card implements Serializable {
011
012    private static final int UNBOUNDED_HIGH = Integer.MAX_VALUE;
013    private final int low;
014    private final int high;
015
016    /**
017     * Construct an unbounded cardinality.
018     */
019    public Card() {
020        this(0, UNBOUNDED_HIGH);
021    }
022
023    /**
024     * Construct a cardinality bounded from below, unbounded above.
025     *
026     * @param low the low cardinality
027     */
028    public Card(int low) {
029        this(low, UNBOUNDED_HIGH);
030    }
031
032    /**
033     * Construct a cardinality bounded from below and above.
034     *
035     * @param low the low cardinality
036     * @param high the high cardinality
037     */
038    public Card(int low, int high) {
039        if (low < 0) {
040            throw new IllegalArgumentException("low(" + low + ") < 0");
041        }
042        if (high < low) {
043            throw new IllegalArgumentException("high(" + high + ") > low(" + low + ")");
044        }
045        this.low = low;
046        this.high = high;
047    }
048
049    /**
050     * Detects if this cardinality is so restrictive that only one possible
051     * value is allowed.
052     *
053     * @return {@code true} if and only if low and high cardinality is the same,
054     * {@code false} otherwise
055     */
056    public boolean isExact() {
057        return low == high;
058    }
059
060    /**
061     * Detects if this cardinality is either bounded below and/or above.
062     *
063     * @return {@code true} if and only if this cardinality is not unbounded,
064     * {@code false} otherwise
065     */
066    public boolean isBounded() {
067        return hasLow() || hasHigh();
068    }
069
070    /**
071     * Detects if this cardinality is bounded below.
072     *
073     * @return {@code true} if and only if this cardinality is bounded below,
074     * {@code false} otherwise
075     */
076    public boolean hasLow() {
077        return low != 0;
078    }
079
080    /**
081     * Returns the low cardinality or 0 if unbounded from below.
082     *
083     * @return the low cardinality
084     */
085    public int getLow() {
086        return low;
087    }
088
089    /**
090     * Detects if this cardinality is bounded above.
091     *
092     * @return {@code true} if and only if this cardinality is bounded above,
093     * {@code false} otherwise
094     */
095    public boolean hasHigh() {
096        return high != UNBOUNDED_HIGH;
097    }
098
099    /**
100     * Returns the high cardinality or Integer.MAX_VALUE if unbounded form
101     * above.
102     *
103     * @return the high cardinality
104     */
105    public int getHigh() {
106        return high;
107    }
108
109    /**
110     * Add two cardinalities together.
111     *
112     * @param addend the other cardinality
113     * @return the sum of this and the other cardinality
114     */
115    public Card add(Card addend) {
116        if (hasHigh() && addend.hasHigh()) {
117            return new Card(low + addend.low, high + addend.high);
118        }
119        return new Card(low + addend.low);
120    }
121
122    /**
123     * Multiply two cardinalities together.
124     *
125     * @param factor the other cardinality
126     * @return the product of this and the other cardinality
127     */
128    public Card mult(Card factor) {
129        if (hasHigh() && factor.hasHigh()) {
130            return new Card(low * factor.low, high * factor.high);
131        }
132        return new Card(low * factor.low);
133    }
134
135    /**
136     * {@inheritDoc}
137     */
138    @Override
139    public boolean equals(Object obj) {
140        if (obj instanceof Card) {
141            Card other = (Card) obj;
142            return low == other.low && high == other.high;
143        }
144        return false;
145    }
146
147    /**
148     * {@inheritDoc}
149     */
150    @Override
151    public int hashCode() {
152        return low ^ high;
153    }
154
155    /**
156     * {@inheritDoc}
157     */
158    @Override
159    public String toString() {
160        if (!hasHigh()) {
161            return hasLow() ? low + "..*" : "*";
162        }
163        return isExact() ? Integer.toString(low) : low + ".." + high;
164    }
165}