1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.util;
18
19import static com.android.internal.util.Preconditions.checkNotNull;
20import static com.android.internal.util.Preconditions.checkArgumentFinite;
21
22/**
23 * Immutable class for describing width and height dimensions in some arbitrary
24 * unit.
25 * <p>
26 * Width and height are finite values stored as a floating point representation.
27 * </p>
28 */
29public final class SizeF {
30    /**
31     * Create a new immutable SizeF instance.
32     *
33     * <p>Both the {@code width} and the {@code height} must be a finite number.
34     * In particular, {@code NaN} and positive/negative infinity are illegal values.</p>
35     *
36     * @param width The width of the size
37     * @param height The height of the size
38     *
39     * @throws IllegalArgumentException
40     *             if either {@code width} or {@code height} was not finite.
41     */
42    public SizeF(final float width, final float height) {
43        mWidth = checkArgumentFinite(width, "width");
44        mHeight = checkArgumentFinite(height, "height");
45    }
46
47    /**
48     * Get the width of the size (as an arbitrary unit).
49     * @return width
50     */
51    public float getWidth() {
52        return mWidth;
53    }
54
55    /**
56     * Get the height of the size (as an arbitrary unit).
57     * @return height
58     */
59    public float getHeight() {
60        return mHeight;
61    }
62
63    /**
64     * Check if this size is equal to another size.
65     *
66     * <p>Two sizes are equal if and only if both their widths and heights are the same.</p>
67     *
68     * <p>For this purpose, the width/height float values are considered to be the same if and only
69     * if the method {@link Float#floatToIntBits(float)} returns the identical {@code int} value
70     * when applied to each.</p>
71     *
72     * @return {@code true} if the objects were equal, {@code false} otherwise
73     */
74    @Override
75    public boolean equals(final Object obj) {
76        if (obj == null) {
77            return false;
78        }
79        if (this == obj) {
80            return true;
81        }
82        if (obj instanceof SizeF) {
83            final SizeF other = (SizeF) obj;
84            return mWidth == other.mWidth && mHeight == other.mHeight;
85        }
86        return false;
87    }
88
89    /**
90     * Return the size represented as a string with the format {@code "WxH"}
91     *
92     * @return string representation of the size
93     */
94    @Override
95    public String toString() {
96        return mWidth + "x" + mHeight;
97    }
98
99    private static NumberFormatException invalidSizeF(String s) {
100        throw new NumberFormatException("Invalid SizeF: \"" + s + "\"");
101    }
102
103    /**
104     * Parses the specified string as a size value.
105     * <p>
106     * The ASCII characters {@code \}{@code u002a} ('*') and
107     * {@code \}{@code u0078} ('x') are recognized as separators between
108     * the width and height.</p>
109     * <p>
110     * For any {@code SizeF s}: {@code SizeF.parseSizeF(s.toString()).equals(s)}.
111     * However, the method also handles sizes expressed in the
112     * following forms:</p>
113     * <p>
114     * "<i>width</i>{@code x}<i>height</i>" or
115     * "<i>width</i>{@code *}<i>height</i>" {@code => new SizeF(width, height)},
116     * where <i>width</i> and <i>height</i> are string floats potentially
117     * containing a sign, such as "-10.3", "+7" or "5.2", but not containing
118     * an {@code 'x'} (such as a float in hexadecimal string format).</p>
119     *
120     * <pre>{@code
121     * SizeF.parseSizeF("3.2*+6").equals(new SizeF(3.2f, 6.0f)) == true
122     * SizeF.parseSizeF("-3x-6").equals(new SizeF(-3.0f, -6.0f)) == true
123     * SizeF.parseSizeF("4 by 3") => throws NumberFormatException
124     * }</pre>
125     *
126     * @param string the string representation of a size value.
127     * @return the size value represented by {@code string}.
128     *
129     * @throws NumberFormatException if {@code string} cannot be parsed
130     * as a size value.
131     * @throws NullPointerException if {@code string} was {@code null}
132     */
133    public static SizeF parseSizeF(String string)
134            throws NumberFormatException {
135        checkNotNull(string, "string must not be null");
136
137        int sep_ix = string.indexOf('*');
138        if (sep_ix < 0) {
139            sep_ix = string.indexOf('x');
140        }
141        if (sep_ix < 0) {
142            throw invalidSizeF(string);
143        }
144        try {
145            return new SizeF(Float.parseFloat(string.substring(0, sep_ix)),
146                    Float.parseFloat(string.substring(sep_ix + 1)));
147        } catch (NumberFormatException e) {
148            throw invalidSizeF(string);
149        } catch (IllegalArgumentException e) {
150            throw invalidSizeF(string);
151        }
152    }
153
154    /**
155     * {@inheritDoc}
156     */
157    @Override
158    public int hashCode() {
159        return Float.floatToIntBits(mWidth) ^ Float.floatToIntBits(mHeight);
160    }
161
162    private final float mWidth;
163    private final float mHeight;
164}
165