001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.util.lang;
018
019import java.text.NumberFormat;
020import java.text.ParseException;
021import java.util.Locale;
022import java.util.regex.Matcher;
023import java.util.regex.Pattern;
024
025import org.apache.wicket.util.string.StringValue;
026import org.apache.wicket.util.string.StringValueConversionException;
027import org.apache.wicket.util.value.LongValue;
028
029
030/**
031 * Represents an immutable byte count. These static factory methods allow easy construction of value
032 * objects using either long values like bytes(2034) or megabytes(3):
033 * <p>
034 * <ul>
035 * <li>Bytes.bytes(long)
036 * <li>Bytes.kilobytes(long)
037 * <li>Bytes.megabytes(long)
038 * <li>Bytes.gigabytes(long)
039 * <li>Bytes.terabytes(long)
040 * </ul>
041 * <p>
042 * or double precision floating point values like megabytes(3.2):
043 * <p>
044 * <ul>
045 * <li>Bytes.bytes(double)
046 * <li>Bytes.kilobytes(double)
047 * <li>Bytes.megabytes(double)
048 * <li>Bytes.gigabytes(double)
049 * <li>Bytes.terabytes(double)
050 * </ul>
051 * <p>
052 * In the case of bytes(double), the value will be rounded off to the nearest integer byte count
053 * using Math.round().
054 * <p>
055 * The precise number of bytes in a Bytes object can be retrieved by calling bytes(). Approximate
056 * values for different units can be retrieved as double precision values using these methods:
057 * <p>
058 * <ul>
059 * <li>kilobytes()
060 * <li>megabytes()
061 * <li>gigabytes()
062 * <li>terabytes()
063 * </ul>
064 * <p>
065 * Also, value objects can be constructed from strings, optionally using a Locale with
066 * valueOf(String) and valueOf(String,Locale). The string may contain a decimal or floating point
067 * number followed by optional whitespace followed by a unit (nothing for bytes, K for kilobyte, M
068 * for megabytes, G for gigabytes or T for terabytes) optionally followed by a B (for bytes). Any of
069 * these letters can be any case. So, examples of permissible string values are:
070 * <p>
071 * <ul>
072 * <li>37 (37 bytes)
073 * <li>2.3K (2.3 kilobytes)
074 * <li>2.5 kb (2.5 kilobytes)
075 * <li>4k (4 kilobytes)
076 * <li>35.2GB (35.2 gigabytes)
077 * <li>1024M (1024 megabytes)
078 * </ul>
079 * <p>
080 * Note that if the Locale was not US, the values might substitute "," for "." as that is the custom
081 * in Euroland.
082 * <p>
083 * The toString() and toString(Locale) methods are smart enough to convert a given value object to
084 * the most appropriate units for the given value.
085 * 
086 * @author Jonathan Locke
087 */
088public final class Bytes extends LongValue
089{
090        private static final long serialVersionUID = 1L;
091
092        /** Pattern for string parsing. */
093        private static final Pattern valuePattern = Pattern.compile(
094                "([0-9]+([\\.,][0-9]+)?)\\s*(|K|M|G|T)B?", Pattern.CASE_INSENSITIVE);
095
096        /** Maximum bytes value */
097        public static final Bytes MAX = bytes(Long.MAX_VALUE);
098
099        /**
100         * Private constructor forces use of static factory methods.
101         * 
102         * @param bytes
103         *            Number of bytes
104         */
105        private Bytes(final long bytes)
106        {
107                super(bytes);
108
109                if (bytes < 0)
110                {
111                        throw new IllegalArgumentException("'bytes' cannot be negative: " + bytes);
112                }
113        }
114
115        /**
116         * Instantiate immutable Bytes value object..
117         * 
118         * @param bytes
119         *            Value to convert
120         * @return Input as Bytes
121         */
122        public static Bytes bytes(final long bytes)
123        {
124                return new Bytes(bytes);
125        }
126
127        /**
128         * Instantiate immutable Bytes value object..
129         * 
130         * @param kilobytes
131         *            Value to convert
132         * @return Input as Bytes
133         */
134        public static Bytes kilobytes(final long kilobytes)
135        {
136                return bytes(kilobytes * 1024);
137        }
138
139        /**
140         * Instantiate immutable Bytes value object..
141         * 
142         * @param megabytes
143         *            Value to convert
144         * @return Input as Bytes
145         */
146        public static Bytes megabytes(final long megabytes)
147        {
148                return kilobytes(megabytes * 1024);
149        }
150
151        /**
152         * Instantiate immutable Bytes value object..
153         * 
154         * @param gigabytes
155         *            Value to convert
156         * @return Input as Bytes
157         */
158        public static Bytes gigabytes(final long gigabytes)
159        {
160                return megabytes(gigabytes * 1024);
161        }
162
163        /**
164         * Instantiate immutable Bytes value object..
165         * 
166         * @param terabytes
167         *            Value to convert
168         * @return Input as Bytes
169         */
170        public static Bytes terabytes(final long terabytes)
171        {
172                return gigabytes(terabytes * 1024);
173        }
174
175        /**
176         * Instantiate immutable Bytes value object..
177         * 
178         * @param bytes
179         *            Value to convert
180         * @return Input as Bytes
181         */
182        public static Bytes bytes(final double bytes)
183        {
184                return bytes(Math.round(bytes));
185        }
186
187        /**
188         * Instantiate immutable Bytes value object..
189         * 
190         * @param kilobytes
191         *            Value to convert
192         * @return Input as Bytes
193         */
194        public static Bytes kilobytes(final double kilobytes)
195        {
196                return bytes(kilobytes * 1024.0);
197        }
198
199        /**
200         * Instantiate immutable Bytes value object..
201         * 
202         * @param megabytes
203         *            Value to convert
204         * @return Input as Bytes
205         */
206        public static Bytes megabytes(final double megabytes)
207        {
208                return kilobytes(megabytes * 1024.0);
209        }
210
211        /**
212         * Instantiate immutable Bytes value object..
213         * 
214         * @param gigabytes
215         *            Value to convert
216         * @return Input as Bytes
217         */
218        public static Bytes gigabytes(final double gigabytes)
219        {
220                return megabytes(gigabytes * 1024.0);
221        }
222
223        /**
224         * Instantiate immutable Bytes value object..
225         * 
226         * @param terabytes
227         *            Value to convert
228         * @return Input as Bytes
229         */
230        public static Bytes terabytes(final double terabytes)
231        {
232                return gigabytes(terabytes * 1024.0);
233        }
234
235        /**
236         * Gets the byte count represented by this value object.
237         * 
238         * @return Byte count
239         */
240        public final long bytes()
241        {
242                return value;
243        }
244
245        /**
246         * Gets the byte count in kilobytes.
247         * 
248         * @return The value in kilobytes
249         */
250        public final double kilobytes()
251        {
252                return value / 1024.0;
253        }
254
255        /**
256         * Gets the byte count in megabytes.
257         * 
258         * @return The value in megabytes
259         */
260        public final double megabytes()
261        {
262                return kilobytes() / 1024.0;
263        }
264
265        /**
266         * Gets the byte count in gigabytes.
267         * 
268         * @return The value in gigabytes
269         */
270        public final double gigabytes()
271        {
272                return megabytes() / 1024.0;
273        }
274
275        /**
276         * Gets the byte count in terabytes.
277         * 
278         * @return The value in terabytes
279         */
280        public final double terabytes()
281        {
282                return gigabytes() / 1024.0;
283        }
284
285        /**
286         * Converts a string to a number of bytes. Strings consist of a floating point value followed by
287         * K, M, G or T for kilobytes, megabytes, gigabytes or terabytes, respectively. The
288         * abbreviations KB, MB, GB and TB are also accepted. Matching is case insensitive.
289         * 
290         * @param string
291         *            The string to convert
292         * @param locale
293         *            The Locale to be used for transformation
294         * @return The Bytes value for the string
295         * @throws StringValueConversionException
296         */
297        public static Bytes valueOf(final String string, final Locale locale)
298                throws StringValueConversionException
299        {
300                final Matcher matcher = valuePattern.matcher(string);
301
302                // Valid input?
303                if (matcher.matches())
304                {
305                        try
306                        {
307                                // Get double precision value
308                                final double value = NumberFormat.getNumberInstance(locale)
309                                        .parse(matcher.group(1))
310                                        .doubleValue();
311
312                                // Get units specified
313                                final String units = matcher.group(3);
314
315                                if (units.equalsIgnoreCase(""))
316                                {
317                                        return bytes(value);
318                                }
319                                else if (units.equalsIgnoreCase("K"))
320                                {
321                                        return kilobytes(value);
322                                }
323                                else if (units.equalsIgnoreCase("M"))
324                                {
325                                        return megabytes(value);
326                                }
327                                else if (units.equalsIgnoreCase("G"))
328                                {
329                                        return gigabytes(value);
330                                }
331                                else if (units.equalsIgnoreCase("T"))
332                                {
333                                        return terabytes(value);
334                                }
335                                else
336                                {
337                                        throw new StringValueConversionException("Units not recognized: " + string);
338                                }
339                        }
340                        catch (ParseException e)
341                        {
342                                throw new StringValueConversionException("Unable to parse numeric part: " + string,
343                                        e);
344                        }
345                }
346                else
347                {
348                        throw new StringValueConversionException("Unable to parse bytes: " + string);
349                }
350        }
351
352        /**
353         * Converts a string to a number of bytes. Strings consist of a floating point value followed by
354         * K, M, G or T for kilobytes, megabytes, gigabytes or terabytes, respectively. The
355         * abbreviations KB, MB, GB and TB are also accepted. Matching is case insensitive.
356         * 
357         * @param string
358         *            The string to convert
359         * @return The Bytes value for the string
360         * @throws StringValueConversionException
361         */
362        public static Bytes valueOf(final String string) throws StringValueConversionException
363        {
364                return valueOf(string, Locale.getDefault(Locale.Category.FORMAT));
365        }
366
367        /**
368         * Converts this byte count to a string using the default locale.
369         * 
370         * @return The string for this byte count
371         */
372        @Override
373        public String toString()
374        {
375                return toString(Locale.getDefault(Locale.Category.FORMAT));
376        }
377
378        /**
379         * Converts this byte count to a string using the given locale.
380         * 
381         * @param locale
382         *            Locale to use for conversion
383         * @return The string for this byte count
384         */
385        public String toString(final Locale locale)
386        {
387                if (terabytes() >= 1.0)
388                {
389                        return unitString(terabytes(), "TB", locale);
390                }
391
392                if (gigabytes() >= 1.0)
393                {
394                        return unitString(gigabytes(), "GB", locale);
395                }
396
397                if (megabytes() >= 1.0)
398                {
399                        return unitString(megabytes(), "MB", locale);
400                }
401
402                if (kilobytes() >= 1.0)
403                {
404                        return unitString(kilobytes(), "KB", locale);
405                }
406
407                return Long.toString(value) + " bytes";
408        }
409
410        /**
411         * Convert value to formatted floating point number and units.
412         * 
413         * @param value
414         *            The value
415         * @param units
416         *            The units
417         * @param locale
418         *            The locale
419         * @return The formatted string
420         */
421        private String unitString(final double value, final String units, final Locale locale)
422        {
423                return StringValue.valueOf(value, locale) + units;
424        }
425
426        /**
427         * Compares this <code>Bytes</code> with another <code>Bytes</code> instance.
428         * 
429         * @param other
430         *            the <code>Bytes</code> instance to compare with
431         * @return <code>true</code> if this <code>Bytes</code> is greater than the given
432         *         <code>Bytes</code> instance
433         */
434        public boolean greaterThan(final Bytes other)
435        {
436                if ((this == other) || (other == null))
437                {
438                        return false;
439                }
440                return bytes() > other.bytes();
441        }
442}