1 /*
2 * Copyright 2006-2016 The JGUIraffe Team.
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 package net.sf.jguiraffe.gui.layout;
17
18 import java.io.Serializable;
19 import java.util.regex.Matcher;
20 import java.util.regex.Pattern;
21
22 /**
23 * <p>
24 * A class that combines a value with a unit.
25 * </p>
26 * <p>
27 * This class can be used to work with values that have an associated unit. For
28 * instance spaces in a layout can be defined in arbitrary units like pixels,
29 * dialog units, inches or centimeters. To support this, an instance of this
30 * class stores a value (as a floating point number) and a reference to a
31 * {@link Unit} object. This <code>Unit</code> object is also used
32 * to perform conversions of the stored value to the default unit pixels.
33 * </p>
34 * <p>
35 * Instances of this class are immutable. Once created, they cannot be changed
36 * any more. Thus they can be shared between multiple threads.
37 * </p>
38 *
39 * @author Oliver Heger
40 * @version $Id: NumberWithUnit.java 205 2012-01-29 18:29:57Z oheger $
41 */
42 public class NumberWithUnit implements Serializable
43 {
44 /** Constant for the special value zero. */
45 public static final NumberWithUnit ZERO;
46
47 /**
48 * The serial version UID.
49 */
50 private static final long serialVersionUID = 20090730L;
51
52 /** Constant for the pattern of a valid unit string. */
53 private static final Pattern PAT_UNITSTR = Pattern
54 .compile("([0-9]*\\.?[0-9]*)\\s*(\\S*)");
55
56 /** Constant for the initial string buffer size. */
57 private static final int BUF_SIZE = 64;
58
59 /** Constant for the number of bits for shifting a long number. */
60 private static final int LONG_SHIFT = 32;
61
62 /** Stores the unit. */
63 private final Unit unit;
64
65 /** Stores the value. */
66 private final double value;
67
68 /**
69 * Creates a new instance of <code>NumberWithUnit</code> with the numeric
70 * value set to 0 and the unit pixels.
71 */
72 private NumberWithUnit()
73 {
74 this(0, Unit.PIXEL);
75 }
76
77 /**
78 * Creates a new instance of <code>NumberWithUnit</code> with the given
79 * value and the unit pixels.
80 *
81 * @param val the value
82 */
83 public NumberWithUnit(int val)
84 {
85 this(val, Unit.PIXEL);
86 }
87
88 /**
89 * Creates a new instance of <code>NumberWithUnit</code> and initializes it.
90 *
91 * @param val the numeric value
92 * @param unit the unit (must not be <b>null </b>)
93 */
94 public NumberWithUnit(double val, Unit unit)
95 {
96 if (unit == null)
97 {
98 throw new IllegalArgumentException("Unit must not be null!");
99 }
100
101 this.unit = unit;
102 value = val;
103 }
104
105 /**
106 * Creates a new instance of {@code NumberWithUnit} and initializes it from
107 * the given string representation. The string passed to this method must
108 * start with a valid double number. Then after optional whitespace the name
109 * of the unit must follow. If no unit is provided, pixel is assumed. Valid
110 * strings are for instance "10", "10cm", "10.5 cm".
111 *
112 * @param s the string to be parsed (must not be <b>null</b>)
113 * @throws IllegalArgumentException if the passed in string is not a valid
114 * unit string
115 * @see #toUnitString()
116 */
117 public NumberWithUnit(String s)
118 {
119 if (s == null)
120 {
121 throw new IllegalArgumentException("Unit string must not be null!");
122 }
123
124 Matcher m = PAT_UNITSTR.matcher(s.trim());
125 if (!m.matches())
126 {
127 throw new IllegalArgumentException("Not a valid unit string: " + s);
128 }
129
130 try
131 {
132 value = Double.parseDouble(m.group(1));
133 }
134 catch (NumberFormatException nfex)
135 {
136 throw new IllegalArgumentException("Not a valid unit string: " + s
137 + ". Not a valid number.");
138 }
139
140 if (m.group(2).length() <= 0)
141 {
142 unit = Unit.PIXEL;
143 }
144 else
145 {
146 unit = Unit.fromString(m.group(2));
147 }
148 }
149
150 /**
151 * Returns the numeric value.
152 *
153 * @return the value
154 */
155 public final double getValue()
156 {
157 return value;
158 }
159
160 /**
161 * Returns the unit of this number.
162 *
163 * @return the unit
164 */
165 public final Unit getUnit()
166 {
167 return unit;
168 }
169
170 /**
171 * Converts this number into a pixel value. This method calls the
172 * corresponding conversion method on the actual <code>Unit</code> object.
173 *
174 * @param handler the size handler
175 * @param comp the component
176 * @param y flag for X or Y direction
177 * @return the pixel value
178 * @see Unit#toPixel(double, UnitSizeHandler, Object, boolean)
179 */
180 public int toPixel(UnitSizeHandler handler, Object comp, boolean y)
181 {
182 return getUnit().toPixel(getValue(), handler, comp, y);
183 }
184
185 /**
186 * Appends a string representation of this number and its unit to the given
187 * string buffer. First the number is written and then the name of the unit.
188 * This is the same as {@link #toUnitString()}, but the string is directly
189 * appended to the given buffer.
190 *
191 * @param buf the string buffer (must not be <b>null</b>)
192 * @throws IllegalArgumentException if the buffer is <b>null</b>
193 */
194 public void buildUnitString(StringBuilder buf)
195 {
196 if (buf == null)
197 {
198 throw new IllegalArgumentException("Buffer must not be null!");
199 }
200
201 if (Unit.PIXEL.equals(getUnit()))
202 {
203 // pixels are always integer numbers
204 buf.append(Unit.PIXEL.toPixel(getValue(), null, null, false));
205 }
206 else
207 {
208 buf.append(getValue());
209 }
210 buf.append(getUnit().getUnitName());
211 }
212
213 /**
214 * Returns a string representation for the stored value and the unit. This
215 * string contains the value immediately followed by the short unit name,
216 * e.g. 10px, 100dlu, 2.5in. Strings in this format can be passed to the
217 * constructor that takes a string argument.
218 *
219 * @return a string for the value and the unit
220 */
221 public String toUnitString()
222 {
223 StringBuilder buf = new StringBuilder();
224 buildUnitString(buf);
225 return buf.toString();
226 }
227
228 /**
229 * Returns a string representation of this object.
230 *
231 * @return a string for this object
232 */
233 @Override
234 public String toString()
235 {
236 StringBuilder buf = new StringBuilder(BUF_SIZE);
237 buf.append("NumberWithUnit [ ");
238 buildUnitString(buf);
239 buf.append(" ]");
240 return buf.toString();
241 }
242
243 /**
244 * Returns a hash code for this object.
245 *
246 * @return a hash code
247 */
248 @Override
249 public int hashCode()
250 {
251 final int factor = 31;
252 final int seed = 17;
253
254 int result = seed;
255 result = factor * result + getUnit().hashCode();
256 long f = Double.doubleToLongBits(getValue());
257 result = factor * result + (int) (f ^ (f >>> LONG_SHIFT));
258
259 return result;
260 }
261
262 /**
263 * Compares two objects. Two instances of this class are equal if they have
264 * the same unit and the same value.
265 *
266 * @param obj the object to compare to
267 * @return a flag if the objects are equal
268 */
269 @Override
270 public boolean equals(Object obj)
271 {
272 if (this == obj)
273 {
274 return true;
275 }
276 if (!(obj instanceof NumberWithUnit))
277 {
278 return false;
279 }
280
281 NumberWithUnit c = (NumberWithUnit) obj;
282 return getUnit() == c.getUnit()
283 && Double.compare(getValue(), c.getValue()) == 0;
284 }
285
286 /**
287 * A convenience method for performing checks for <b>null</b> values. This
288 * method converts a <b>null</b> argument into an instance with the value 0.
289 * Other arguments are directly returned. This is useful if <b>null</b>
290 * references are to be avoided.
291 *
292 * @param n the source number
293 * @return the guaranteed non null result number
294 */
295 public static NumberWithUnit nonNull(NumberWithUnit n)
296 {
297 return (n != null) ? n : ZERO;
298 }
299
300 static
301 {
302 ZERO = new NumberWithUnit();
303 }
304 }