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     */
016    package org.kuali.common.util;
017    
018    import java.text.NumberFormat;
019    import java.text.ParseException;
020    import java.text.SimpleDateFormat;
021    import java.util.ArrayList;
022    import java.util.Arrays;
023    import java.util.Date;
024    import java.util.List;
025    
026    import org.apache.commons.lang3.StringUtils;
027    
028    /**
029     * Format time, bytes, counts, dates, and transfer rates into human friendly form
030     * 
031     * @author Jeff Caddel
032     * @since May 27, 2010 6:46:17 PM
033     */
034    public class FormatUtils {
035    
036            public static final double SECOND = 1000;
037            public static final double MINUTE = 60 * SECOND;
038            public static final double HOUR = 60 * MINUTE;
039            public static final double DAY = 24 * HOUR;
040            public static final double YEAR = 365 * DAY;
041    
042            private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ";
043    
044            private static final List<String> TIME_TOKENS = Arrays.asList("ms", "s", "m", "h", "d", "y");
045            private static final List<Long> TIME_MULTIPLIERS = getTimeMultipliers();
046    
047            private static final List<String> SIZE_TOKENS = Arrays.asList("b", "k", "m", "g", "t", "p", "e");
048            private static final int BASE = 1024;
049    
050            private static NumberFormat largeSizeFormatter = NumberFormat.getInstance();
051            private static NumberFormat sizeFormatter = NumberFormat.getInstance();
052            private static NumberFormat timeFormatter = NumberFormat.getInstance();
053            private static NumberFormat rateFormatter = NumberFormat.getInstance();
054            private static NumberFormat countFormatter = NumberFormat.getInstance();
055    
056            static {
057                    sizeFormatter.setGroupingUsed(false);
058                    sizeFormatter.setMaximumFractionDigits(1);
059                    sizeFormatter.setMinimumFractionDigits(1);
060                    largeSizeFormatter.setGroupingUsed(false);
061                    largeSizeFormatter.setMaximumFractionDigits(3);
062                    largeSizeFormatter.setMinimumFractionDigits(3);
063                    timeFormatter.setGroupingUsed(false);
064                    timeFormatter.setMaximumFractionDigits(3);
065                    timeFormatter.setMinimumFractionDigits(3);
066                    rateFormatter.setGroupingUsed(false);
067                    rateFormatter.setMaximumFractionDigits(3);
068                    rateFormatter.setMinimumFractionDigits(3);
069                    countFormatter.setGroupingUsed(true);
070                    countFormatter.setMaximumFractionDigits(0);
071                    countFormatter.setMinimumFractionDigits(0);
072            }
073    
074            /**
075             * 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.
076             * 
077             * <pre>
078             *   1  == 1 byte
079             *   1b == 1 byte
080             *   1k == 1 kilobyte == 1024   bytes ==                     1,024 bytes
081             *   1m == 1 megabyte == 1024^2 bytes ==                 1,048,576 bytes
082             *   1g == 1 gigabyte == 1024^3 bytes ==             1,073,741,824 bytes
083             *   1t == 1 terabyte == 1024^4 bytes ==         1,099,511,627,776 bytes
084             *   1p == 1 petabyte == 1024^5 bytes ==     1,125,899,906,842,624 bytes
085             *   1e == 1 exabyte  == 1024^6 bytes == 1,152,921,504,606,846,976 bytes
086             * </pre>
087             */
088            public static long getBytes(String size) {
089                    return getBytes(size, SIZE_TOKENS, BASE);
090            }
091    
092            public static long getBytes(String size, List<String> tokens, int base) {
093                    Assert.notBlank(size);
094                    for (int i = 0; i < tokens.size(); i++) {
095                            String token = tokens.get(i);
096                            long multiplier = (long) Math.pow(base, i);
097                            if (StringUtils.endsWithIgnoreCase(size, token)) {
098                                    return getByteValue(size, token, multiplier);
099                            }
100                    }
101                    // Assume bytes
102                    return getByteValue(size, "", 1);
103            }
104    
105            protected static long getByteValue(String time, String suffix, long multiplier) {
106                    int len = StringUtils.length(time);
107                    String substring = StringUtils.substring(time, 0, len - suffix.length());
108                    Double value = new Double(substring);
109                    value = value * multiplier;
110                    return value.longValue();
111            }
112    
113            /**
114             * 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.
115             * 
116             * <pre>
117             *   1   == 1 millisecond
118             *   1ms == 1 millisecond
119             *   1s  == 1 second ==           1000 milliseconds
120             *   1m  == 1 minute ==         60,000 milliseconds
121             *   1h  == 1 hour   ==      3,600,000 milliseconds 
122             *   1d  == 1 day    ==     86,400,000 milliseconds
123             *   1y  == 1 year   == 31,536,000,000 milliseconds
124             * </pre>
125             */
126            public static long getMillis(String time) {
127                    return getMillis(time, TIME_TOKENS, TIME_MULTIPLIERS);
128            }
129    
130            public static long getMillis(String time, List<String> tokens, List<Long> multipliers) {
131                    Assert.notBlank(time);
132                    Assert.isTrue(tokens.size() == multipliers.size());
133                    for (int i = 0; i < tokens.size(); i++) {
134                            String token = tokens.get(i);
135                            long multiplier = multipliers.get(i);
136                            if (StringUtils.endsWithIgnoreCase(time, token)) {
137                                    return getTimeValue(time, token, multiplier);
138                            }
139                    }
140                    // Assume milliseconds
141                    return getTimeValue(time, "", 1);
142            }
143    
144            protected static long getTimeValue(String time, String suffix, long multiplier) {
145                    int len = StringUtils.length(time);
146                    String substring = StringUtils.substring(time, 0, len - suffix.length());
147                    Double value = new Double(substring);
148                    value = value * multiplier;
149                    return value.longValue();
150            }
151    
152            /**
153             * Parse a date from the string. The string must be in the same format returned by the getDate() methods
154             */
155            public static Date parseDate(String date) {
156                    try {
157                            // New object every time because SimpleDateFormat isn't threadsafe
158                            SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
159                            return sdf.parse(date);
160                    } catch (ParseException e) {
161                            throw new IllegalArgumentException("Can't parse [" + date + "]", e);
162                    }
163            }
164    
165            /**
166             * Return a formatted date
167             */
168            public static String getDate(long millis) {
169                    return getDate(new Date(millis));
170            }
171    
172            /**
173             * Return a formatted date
174             */
175            public static String getDate(Date date) {
176                    // New object every time because SimpleDateFormat isn't threadsafe
177                    SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
178                    return sdf.format(date);
179            }
180    
181            /**
182             * 
183             */
184            public static String getThroughputInSeconds(long millis, long count, String label) {
185                    double seconds = millis / SECOND;
186                    double countPerSecond = count / seconds;
187                    return countFormatter.format(countPerSecond) + " " + label;
188            }
189    
190            /**
191             * 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
192             */
193            public static String getRate(long millis, long bytes) {
194                    double seconds = millis / SECOND;
195                    double bytesPerSecond = bytes / seconds;
196                    Size bandwidthLevel = getSizeEnum(bytesPerSecond);
197                    double transferRate = bytesPerSecond / bandwidthLevel.getValue();
198                    return rateFormatter.format(transferRate) + " " + bandwidthLevel.getRateLabel();
199            }
200    
201            /**
202             * Return a formatted <code>count</code>
203             */
204            public static String getCount(long count) {
205                    return countFormatter.format(count);
206            }
207    
208            /**
209             * Given milliseconds, return milliseconds, seconds, minutes, hours, days, or years as appropriate. Note that years is approximate since the logic always assumes there are
210             * exactly 365 days per year.
211             */
212            public static String getTime(long millis) {
213                    long abs = Math.abs(millis);
214                    if (abs < SECOND) {
215                            return millis + "ms";
216                    } else if (abs < MINUTE) {
217                            return timeFormatter.format(millis / SECOND) + "s";
218                    } else if (abs < HOUR) {
219                            return timeFormatter.format(millis / MINUTE) + "m";
220                    } else if (abs < DAY) {
221                            return timeFormatter.format(millis / HOUR) + "h";
222                    } else if (abs < YEAR) {
223                            return timeFormatter.format(millis / DAY) + "d";
224                    } else {
225                            return timeFormatter.format(millis / YEAR) + "y";
226                    }
227            }
228    
229            /**
230             * Given a number of bytes return bytes, kilobytes, megabytes, gigabytes, terabytes, petabytes, or exabytes as appropriate.
231             */
232            public static String getSize(long bytes) {
233                    return getSize(bytes, null);
234            }
235    
236            /**
237             * Given a number of bytes return a string formatted into the unit of measure indicated
238             */
239            public static String getSize(long bytes, Size unitOfMeasure) {
240                    unitOfMeasure = (unitOfMeasure == null) ? getSizeEnum(bytes) : unitOfMeasure;
241                    StringBuilder sb = new StringBuilder();
242                    sb.append(getFormattedSize(bytes, unitOfMeasure));
243                    sb.append(unitOfMeasure.getSizeLabel());
244                    return sb.toString();
245            }
246    
247            public static String getFormattedSize(long bytes, Size size) {
248                    switch (size) {
249                    case BYTE:
250                            return bytes + "";
251                    case KB:
252                    case MB:
253                    case GB:
254                            return sizeFormatter.format(bytes / (double) size.getValue());
255                    default:
256                            return largeSizeFormatter.format(bytes / (double) size.getValue());
257                    }
258            }
259    
260            public static Size getSizeEnum(double bytes) {
261                    bytes = Math.abs(bytes);
262                    if (bytes < Size.KB.getValue()) {
263                            return Size.BYTE;
264                    } else if (bytes < Size.MB.getValue()) {
265                            return Size.KB;
266                    } else if (bytes < Size.GB.getValue()) {
267                            return Size.MB;
268                    } else if (bytes < Size.TB.getValue()) {
269                            return Size.GB;
270                    } else if (bytes < Size.PB.getValue()) {
271                            return Size.TB;
272                    } else if (bytes < Size.EB.getValue()) {
273                            return Size.PB;
274                    } else {
275                            return Size.EB;
276                    }
277            }
278    
279            protected static final List<Long> getTimeMultipliers() {
280                    List<Long> m = new ArrayList<Long>();
281                    m.add(1L);
282                    m.add(new Double(SECOND).longValue());
283                    m.add(new Double(MINUTE).longValue());
284                    m.add(new Double(HOUR).longValue());
285                    m.add(new Double(DAY).longValue());
286                    m.add(new Double(YEAR).longValue());
287                    return m;
288            }
289    }