001/**
002 * Copyright 2010-2013 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.common.util;
017
018import static java.util.concurrent.TimeUnit.MILLISECONDS;
019
020import java.text.NumberFormat;
021import java.text.ParseException;
022import java.text.SimpleDateFormat;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.Date;
026import java.util.List;
027
028import org.apache.commons.lang3.StringUtils;
029import org.kuali.common.util.base.Exceptions;
030
031import com.google.common.base.Stopwatch;
032
033/**
034 * Format time, bytes, counts, dates, and transfer rates into human friendly form
035 * 
036 * @author Jeff Caddel
037 * @since May 27, 2010 6:46:17 PM
038 */
039public class FormatUtils {
040
041        public static final double SECOND = 1000;
042        public static final double MINUTE = 60 * SECOND;
043        public static final double HOUR = 60 * MINUTE;
044        public static final double DAY = 24 * HOUR;
045        public static final double YEAR = 365 * DAY;
046
047        private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ";
048        private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat(DATE_FORMAT);
049
050        private static final List<String> TIME_TOKENS = Arrays.asList("ms", "s", "m", "h", "d", "y");
051        private static final List<Long> TIME_MULTIPLIERS = getTimeMultipliers();
052
053        private static final List<String> SIZE_TOKENS = Arrays.asList("b", "k", "m", "g", "t", "p", "e");
054        private static final int BASE = 1024;
055
056        private static NumberFormat largeSizeFormatter = NumberFormat.getInstance();
057        private static NumberFormat sizeFormatter = NumberFormat.getInstance();
058        private static NumberFormat timeFormatter = NumberFormat.getInstance();
059        private static NumberFormat rateFormatter = NumberFormat.getInstance();
060        private static NumberFormat countFormatter = NumberFormat.getInstance();
061        private static NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();
062        private static NumberFormat integerFormatter = NumberFormat.getInstance();
063
064        static {
065                integerFormatter.setGroupingUsed(false);
066                integerFormatter.setMaximumFractionDigits(0);
067                integerFormatter.setMinimumFractionDigits(0);
068                sizeFormatter.setGroupingUsed(false);
069                sizeFormatter.setMaximumFractionDigits(1);
070                sizeFormatter.setMinimumFractionDigits(1);
071                largeSizeFormatter.setGroupingUsed(false);
072                largeSizeFormatter.setMaximumFractionDigits(3);
073                largeSizeFormatter.setMinimumFractionDigits(3);
074                timeFormatter.setGroupingUsed(false);
075                timeFormatter.setMaximumFractionDigits(3);
076                timeFormatter.setMinimumFractionDigits(3);
077                rateFormatter.setGroupingUsed(false);
078                rateFormatter.setMaximumFractionDigits(3);
079                rateFormatter.setMinimumFractionDigits(3);
080                countFormatter.setGroupingUsed(true);
081                countFormatter.setMaximumFractionDigits(0);
082                countFormatter.setMinimumFractionDigits(0);
083        }
084
085        public static String getCurrency(double number) {
086                return currencyFormatter.format(number);
087        }
088
089        /**
090         * Parse bytes from a size string that ends with a unit of measure. If no unit of measure is provided, bytes is assumed. Unit of measure is case insensitive.
091         * 
092         * <pre>
093         *   1  == 1 byte
094         *   1b == 1 byte
095         *   1k == 1 kilobyte == 1024   bytes ==                     1,024 bytes
096         *   1m == 1 megabyte == 1024^2 bytes ==                 1,048,576 bytes
097         *   1g == 1 gigabyte == 1024^3 bytes ==             1,073,741,824 bytes
098         *   1t == 1 terabyte == 1024^4 bytes ==         1,099,511,627,776 bytes
099         *   1p == 1 petabyte == 1024^5 bytes ==     1,125,899,906,842,624 bytes
100         *   1e == 1 exabyte  == 1024^6 bytes == 1,152,921,504,606,846,976 bytes
101         * </pre>
102         */
103        public static long getBytes(String size) {
104                return getBytes(size, SIZE_TOKENS, BASE);
105        }
106
107        public static long getBytes(String size, List<String> tokens, int base) {
108                Assert.notBlank(size);
109                for (int i = 0; i < tokens.size(); i++) {
110                        String token = tokens.get(i);
111                        long multiplier = (long) Math.pow(base, i);
112                        if (StringUtils.endsWithIgnoreCase(size, token)) {
113                                return getByteValue(size, token, multiplier);
114                        }
115                }
116                // Assume bytes
117                return getByteValue(size, "", 1);
118        }
119
120        protected static long getByteValue(String time, String suffix, long multiplier) {
121                int len = StringUtils.length(time);
122                String substring = StringUtils.substring(time, 0, len - suffix.length());
123                Double value = new Double(substring);
124                value = value * multiplier;
125                return value.longValue();
126        }
127
128        /**
129         * Parse milliseconds from a time string that ends with a unit of measure. If no unit of measure is provided, milliseconds is assumed. Unit of measure is case insensitive.
130         * 
131         * <pre>
132         *   1   == 1 millisecond
133         *   1ms == 1 millisecond
134         *   1s  == 1 second ==           1000 milliseconds
135         *   1m  == 1 minute ==         60,000 milliseconds
136         *   1h  == 1 hour   ==      3,600,000 milliseconds 
137         *   1d  == 1 day    ==     86,400,000 milliseconds
138         *   1y  == 1 year   == 31,536,000,000 milliseconds
139         * </pre>
140         */
141        public static long getMillis(String time) {
142                return getMillis(time, TIME_TOKENS, TIME_MULTIPLIERS);
143        }
144
145        /**
146         * Parse milliseconds from a time string that ends with a unit of measure. If no unit of measure is provided, milliseconds is assumed. Unit of measure is case insensitive.
147         * 
148         * <pre>
149         *   1   == 1 millisecond
150         *   1ms == 1 millisecond
151         *   1s  == 1 second ==           1000 milliseconds
152         *   1m  == 1 minute ==         60,000 milliseconds
153         *   1h  == 1 hour   ==      3,600,000 milliseconds 
154         *   1d  == 1 day    ==     86,400,000 milliseconds
155         *   1y  == 1 year   == 31,536,000,000 milliseconds
156         * </pre>
157         */
158        public static int getMillisAsInt(String time) {
159                Long millis = getMillis(time);
160                if (millis <= Integer.MAX_VALUE) {
161                        return millis.intValue();
162                } else {
163                        throw Exceptions.illegalArg("[%s] converts to [%s]. maximum allowable integer value is [%s]", time, millis, Integer.MAX_VALUE);
164                }
165        }
166
167        public static long getMillis(String time, List<String> tokens, List<Long> multipliers) {
168                Assert.notBlank(time);
169                Assert.isTrue(tokens.size() == multipliers.size());
170                for (int i = 0; i < tokens.size(); i++) {
171                        String token = tokens.get(i);
172                        long multiplier = multipliers.get(i);
173                        if (StringUtils.endsWithIgnoreCase(time, token)) {
174                                return getTimeValue(time, token, multiplier);
175                        }
176                }
177                // Assume milliseconds
178                return getTimeValue(time, "", 1);
179        }
180
181        protected static long getTimeValue(String time, String suffix, long multiplier) {
182                int len = StringUtils.length(time);
183                String substring = StringUtils.substring(time, 0, len - suffix.length());
184                Double value = new Double(substring);
185                value = value * multiplier;
186                return value.longValue();
187        }
188
189        /**
190         * Parse a date from the string. The string must be in the same format returned by the getDate() methods
191         */
192        public static Date parseDate(String date) {
193                try {
194                        synchronized (DATE_FORMATTER) {
195                                return DATE_FORMATTER.parse(date);
196                        }
197                } catch (ParseException e) {
198                        throw new IllegalArgumentException("Can't parse [" + date + "]", e);
199                }
200        }
201
202        /**
203         * Return a formatted date
204         */
205        public static String getDate(long millis) {
206                return getDate(new Date(millis));
207        }
208
209        /**
210         * Return a formatted date
211         */
212        public static String getDate(Date date) {
213                synchronized (DATE_FORMATTER) {
214                        return DATE_FORMATTER.format(date);
215                }
216        }
217
218        /**
219         * 
220         */
221        public static String getThroughputInSeconds(long millis, long count, String label) {
222                double seconds = millis / SECOND;
223                double countPerSecond = count / seconds;
224                synchronized (countFormatter) {
225                        return countFormatter.format(countPerSecond) + " " + label;
226                }
227        }
228
229        /**
230         * Given a number of bytes and the number of milliseconds it took to transfer that number of bytes, return bytes/s, KB/s, MB/s, GB/s, TB/s, PB/s, or EB/s as appropriate
231         */
232        public static String getRate(long millis, long bytes) {
233                double seconds = millis / SECOND;
234                double bytesPerSecond = bytes / seconds;
235                Size bandwidthLevel = getSizeEnum(bytesPerSecond);
236                double transferRate = bytesPerSecond / bandwidthLevel.getValue();
237                synchronized (rateFormatter) {
238                        return rateFormatter.format(transferRate) + " " + bandwidthLevel.getRateLabel();
239                }
240        }
241
242        /**
243         * Return a formatted <code>count</code>
244         */
245        public static String getCount(long count) {
246                synchronized (countFormatter) {
247                        return countFormatter.format(count);
248                }
249        }
250
251        /**
252         * Given milliseconds, return milliseconds, seconds, minutes, hours, days, or years as appropriate. Note that years is approximate since the logic always assumes there are
253         * exactly 365 days per year.
254         */
255        public static String getTime(long millis) {
256                return getTime(millis, timeFormatter);
257        }
258
259        /**
260         * Given a stopwatch, return milliseconds, seconds, minutes, hours, days, or years as appropriate. Note that years is approximate since the logic always assumes there are
261         * exactly 365 days per year.
262         */
263        public static String getTime(Stopwatch stopwatch) {
264                return getTime(stopwatch.elapsed(MILLISECONDS));
265        }
266
267        /**
268         * Given milliseconds, return milliseconds, seconds, minutes, hours, days, or years as appropriate. Note that years is approximate since the logic always assumes there are
269         * exactly 365 days per year.
270         */
271        public static String getTime(long millis, NumberFormat formatter) {
272                long abs = Math.abs(millis);
273                synchronized (formatter) {
274                        if (abs < SECOND) {
275                                return millis + "ms";
276                        } else if (abs < MINUTE) {
277                                return formatter.format(millis / SECOND) + "s";
278                        } else if (abs < HOUR) {
279                                return formatter.format(millis / MINUTE) + "m";
280                        } else if (abs < DAY) {
281                                return formatter.format(millis / HOUR) + "h";
282                        } else if (abs < YEAR) {
283                                return formatter.format(millis / DAY) + "d";
284                        } else {
285                                return formatter.format(millis / YEAR) + "y";
286                        }
287                }
288        }
289
290        /**
291         * Given a number of bytes return bytes, kilobytes, megabytes, gigabytes, terabytes, petabytes, or exabytes as appropriate.
292         */
293        public static String getSize(long bytes) {
294                return getSize(bytes, null);
295        }
296
297        /**
298         * Given a number of bytes return bytes, kilobytes, megabytes, gigabytes, terabytes, petabytes, or exabytes as appropriate.
299         */
300        public static String getIntegerSize(long bytes) {
301                return getIntegerSize(bytes, null);
302        }
303
304        /**
305         * Given a number of bytes return a string formatted into the unit of measure indicated
306         */
307        public static String getIntegerSize(long bytes, final Size unitOfMeasure) {
308                Size uom = (unitOfMeasure == null) ? getSizeEnum(bytes) : unitOfMeasure;
309                StringBuilder sb = new StringBuilder();
310                synchronized (integerFormatter) {
311                        sb.append(integerFormatter.format(bytes / (double) uom.getValue()));
312                }
313                sb.append(uom.getSizeLabel());
314                return sb.toString();
315        }
316
317        /**
318         * Given a number of bytes return a string formatted into the unit of measure indicated
319         */
320        public static String getSize(long bytes, Size unitOfMeasure) {
321                Size uom = (unitOfMeasure == null) ? getSizeEnum(bytes) : unitOfMeasure;
322                StringBuilder sb = new StringBuilder();
323                sb.append(getFormattedSize(bytes, uom));
324                sb.append(uom.getSizeLabel());
325                return sb.toString();
326        }
327
328        public static String getFormattedSize(long bytes, Size size) {
329                switch (size) {
330                case BYTE:
331                        return bytes + "";
332                case KB:
333                case MB:
334                case GB:
335                        synchronized (sizeFormatter) {
336                                return sizeFormatter.format(bytes / (double) size.getValue());
337                        }
338                default:
339                        synchronized (largeSizeFormatter) {
340                                return largeSizeFormatter.format(bytes / (double) size.getValue());
341                        }
342                }
343        }
344
345        public static Size getSizeEnum(double bytes) {
346                bytes = Math.abs(bytes);
347                if (bytes < Size.KB.getValue()) {
348                        return Size.BYTE;
349                } else if (bytes < Size.MB.getValue()) {
350                        return Size.KB;
351                } else if (bytes < Size.GB.getValue()) {
352                        return Size.MB;
353                } else if (bytes < Size.TB.getValue()) {
354                        return Size.GB;
355                } else if (bytes < Size.PB.getValue()) {
356                        return Size.TB;
357                } else if (bytes < Size.EB.getValue()) {
358                        return Size.PB;
359                } else {
360                        return Size.EB;
361                }
362        }
363
364        protected static final List<Long> getTimeMultipliers() {
365                List<Long> m = new ArrayList<Long>();
366                m.add(1L);
367                m.add(new Double(SECOND).longValue());
368                m.add(new Double(MINUTE).longValue());
369                m.add(new Double(HOUR).longValue());
370                m.add(new Double(DAY).longValue());
371                m.add(new Double(YEAR).longValue());
372                return m;
373        }
374}