View Javadoc

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 }