001/**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019
020package org.apache.hadoop.hbase.util;
021
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.List;
025import java.util.Objects;
026import java.util.regex.Matcher;
027import java.util.regex.Pattern;
028import org.apache.hadoop.hbase.HConstants;
029import org.apache.hadoop.hbase.exceptions.HBaseException;
030import org.apache.yetus.audience.InterfaceAudience;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034@InterfaceAudience.Private
035public final class PrettyPrinter {
036
037  private static final Logger LOG = LoggerFactory.getLogger(PrettyPrinter.class);
038
039  private static final String INTERVAL_REGEX = "((\\d+)\\s*SECONDS?\\s*\\()?\\s*" +
040          "((\\d+)\\s*DAYS?)?\\s*((\\d+)\\s*HOURS?)?\\s*" +
041          "((\\d+)\\s*MINUTES?)?\\s*((\\d+)\\s*SECONDS?)?\\s*\\)?";
042  private static final Pattern INTERVAL_PATTERN = Pattern.compile(INTERVAL_REGEX,
043          Pattern.CASE_INSENSITIVE);
044  private static final String SIZE_REGEX = "((\\d+)\\s*B?\\s*\\()?\\s*" +
045    "((\\d+)\\s*TB?)?\\s*((\\d+)\\s*GB?)?\\s*" +
046    "((\\d+)\\s*MB?)?\\s*((\\d+)\\s*KB?)?\\s*((\\d+)\\s*B?)?\\s*\\)?";
047  private static final Pattern SIZE_PATTERN = Pattern.compile(SIZE_REGEX,
048    Pattern.CASE_INSENSITIVE);
049
050  public enum Unit {
051    TIME_INTERVAL,
052    LONG,
053    BOOLEAN,
054    BYTE,
055    NONE
056  }
057
058  public static String format(final String value, final Unit unit) {
059    StringBuilder human = new StringBuilder();
060    switch (unit) {
061      case TIME_INTERVAL:
062        human.append(humanReadableTTL(Long.parseLong(value)));
063        break;
064      case LONG:
065        byte[] longBytes = Bytes.toBytesBinary(value);
066        human.append(String.valueOf(Bytes.toLong(longBytes)));
067        break;
068      case BOOLEAN:
069        byte[] booleanBytes = Bytes.toBytesBinary(value);
070        human.append(String.valueOf(Bytes.toBoolean(booleanBytes)));
071        break;
072      case BYTE:
073        human.append(humanReadableByte(Long.parseLong(value)));
074        break;
075      default:
076        human.append(value);
077    }
078    return human.toString();
079  }
080
081  /**
082   * Convert a human readable string to its value.
083   * @see org.apache.hadoop.hbase.util.PrettyPrinter#format(String, Unit)
084   * @param pretty
085   * @param unit
086   * @return the value corresponding to the human readable string
087   */
088  public static String valueOf(final String pretty, final Unit unit) throws HBaseException {
089    StringBuilder value = new StringBuilder();
090    switch (unit) {
091      case TIME_INTERVAL:
092        value.append(humanReadableIntervalToSec(pretty));
093        break;
094      case BYTE:
095        value.append(humanReadableSizeToBytes(pretty));
096        break;
097      default:
098        value.append(pretty);
099    }
100    return value.toString();
101  }
102
103  @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="ICAST_INTEGER_MULTIPLY_CAST_TO_LONG",
104      justification="Will not overflow")
105  private static String humanReadableTTL(final long interval){
106    StringBuilder sb = new StringBuilder();
107    int days, hours, minutes, seconds;
108
109    // edge cases first
110    if (interval == Integer.MAX_VALUE) {
111      sb.append("FOREVER");
112      return sb.toString();
113    }
114    if (interval < HConstants.MINUTE_IN_SECONDS) {
115      sb.append(interval);
116      sb.append(" SECOND").append(interval == 1 ? "" : "S");
117      return sb.toString();
118    }
119
120    days  =   (int) (interval / HConstants.DAY_IN_SECONDS);
121    hours =   (int) (interval - HConstants.DAY_IN_SECONDS * days) / HConstants.HOUR_IN_SECONDS;
122    minutes = (int) (interval - HConstants.DAY_IN_SECONDS * days
123        - HConstants.HOUR_IN_SECONDS * hours) / HConstants.MINUTE_IN_SECONDS;
124    seconds = (int) (interval - HConstants.DAY_IN_SECONDS * days
125        - HConstants.HOUR_IN_SECONDS * hours - HConstants.MINUTE_IN_SECONDS * minutes);
126
127    sb.append(interval);
128    sb.append(" SECONDS (");
129
130    if (days > 0) {
131      sb.append(days);
132      sb.append(" DAY").append(days == 1 ? "" : "S");
133    }
134
135    if (hours > 0) {
136      sb.append(days > 0 ? " " : "");
137      sb.append(hours);
138      sb.append(" HOUR").append(hours == 1 ? "" : "S");
139    }
140
141    if (minutes > 0) {
142      sb.append(days + hours > 0 ? " " : "");
143      sb.append(minutes);
144      sb.append(" MINUTE").append(minutes == 1 ? "" : "S");
145    }
146
147    if (seconds > 0) {
148      sb.append(days + hours + minutes > 0 ? " " : "");
149      sb.append(seconds);
150      sb.append(" SECOND").append(minutes == 1 ? "" : "S");
151    }
152
153    sb.append(")");
154
155    return sb.toString();
156  }
157
158  /**
159   * Convert a human readable time interval to seconds. Examples of the human readable
160   * time intervals are: 50 DAYS 1 HOUR 30 MINUTES , 25000 SECONDS etc.
161   * The units of time specified can be in uppercase as well as lowercase. Also, if a
162   * single number is specified without any time unit, it is assumed to be in seconds.
163   * @param humanReadableInterval
164   * @return value in seconds
165   */
166  private static long humanReadableIntervalToSec(final String humanReadableInterval)
167          throws HBaseException {
168    if (humanReadableInterval == null || humanReadableInterval.equalsIgnoreCase("FOREVER")) {
169      return HConstants.FOREVER;
170    }
171
172    try {
173      return Long.parseLong(humanReadableInterval);
174    } catch(NumberFormatException ex) {
175      LOG.debug("Given interval value is not a number, parsing for human readable format");
176    }
177
178    String days = null;
179    String hours = null;
180    String minutes = null;
181    String seconds = null;
182    String expectedTtl = null;
183    long ttl;
184
185    Matcher matcher = PrettyPrinter.INTERVAL_PATTERN.matcher(humanReadableInterval);
186    if (matcher.matches()) {
187      expectedTtl = matcher.group(2);
188      days = matcher.group(4);
189      hours = matcher.group(6);
190      minutes = matcher.group(8);
191      seconds = matcher.group(10);
192    }
193    ttl = 0;
194    ttl += days != null ? Long.parseLong(days)*HConstants.DAY_IN_SECONDS:0;
195    ttl += hours != null ? Long.parseLong(hours)*HConstants.HOUR_IN_SECONDS:0;
196    ttl += minutes != null ? Long.parseLong(minutes)*HConstants.MINUTE_IN_SECONDS:0;
197    ttl += seconds != null ? Long.parseLong(seconds):0;
198
199    if (expectedTtl != null && Long.parseLong(expectedTtl) != ttl) {
200      throw new HBaseException("Malformed TTL string: TTL values in seconds and human readable" +
201              "format do not match");
202    }
203    return ttl;
204  }
205
206  /**
207   * Convert a long size to a human readable string.
208   * Example: 10763632640 -> 10763632640 B (10GB 25MB)
209   * @param size the size in bytes
210   * @return human readable string
211   */
212  private static String humanReadableByte(final long size) {
213    StringBuilder sb = new StringBuilder();
214    long tb, gb, mb, kb, b;
215
216    if (size < HConstants.KB_IN_BYTES) {
217      sb.append(size);
218      sb.append(" B");
219      return sb.toString();
220    }
221
222    tb = size / HConstants.TB_IN_BYTES;
223    gb = (size - HConstants.TB_IN_BYTES * tb) / HConstants.GB_IN_BYTES;
224    mb = (size - HConstants.TB_IN_BYTES * tb
225      - HConstants.GB_IN_BYTES * gb) / HConstants.MB_IN_BYTES;
226    kb = (size - HConstants.TB_IN_BYTES * tb - HConstants.GB_IN_BYTES * gb
227      - HConstants.MB_IN_BYTES * mb) / HConstants.KB_IN_BYTES;
228    b = (size - HConstants.TB_IN_BYTES * tb - HConstants.GB_IN_BYTES * gb
229      - HConstants.MB_IN_BYTES * mb - HConstants.KB_IN_BYTES * kb);
230
231    sb.append(size).append(" B (");
232    if (tb > 0) {
233      sb.append(tb);
234      sb.append("TB");
235    }
236
237    if (gb > 0) {
238      sb.append(tb > 0 ? " " : "");
239      sb.append(gb);
240      sb.append("GB");
241    }
242
243    if (mb > 0) {
244      sb.append(tb + gb > 0 ? " " : "");
245      sb.append(mb);
246      sb.append("MB");
247    }
248
249    if (kb > 0) {
250      sb.append(tb + gb + mb > 0 ? " " : "");
251      sb.append(kb);
252      sb.append("KB");
253    }
254
255    if (b > 0) {
256      sb.append(tb + gb + mb + kb > 0 ? " " : "");
257      sb.append(b);
258      sb.append("B");
259    }
260
261    sb.append(")");
262    return sb.toString();
263  }
264
265  /**
266   * Convert a human readable size to bytes.
267   * Examples of the human readable size are: 50 GB 20 MB 1 KB , 25000 B etc.
268   * The units of size specified can be in uppercase as well as lowercase. Also, if a
269   * single number is specified without any time unit, it is assumed to be in bytes.
270   * @param humanReadableSize human readable size
271   * @return value in bytes
272   * @throws HBaseException
273   */
274  private static long humanReadableSizeToBytes(final String humanReadableSize)
275      throws HBaseException {
276    if (humanReadableSize == null) {
277      return -1;
278    }
279
280    try {
281      return Long.parseLong(humanReadableSize);
282    } catch(NumberFormatException ex) {
283      LOG.debug("Given size value is not a number, parsing for human readable format");
284    }
285
286    String tb = null;
287    String gb = null;
288    String mb = null;
289    String kb = null;
290    String b = null;
291    String expectedSize = null;
292    long size = 0;
293
294    Matcher matcher = PrettyPrinter.SIZE_PATTERN.matcher(humanReadableSize);
295    if (matcher.matches()) {
296      expectedSize = matcher.group(2);
297      tb = matcher.group(4);
298      gb = matcher.group(6);
299      mb = matcher.group(8);
300      kb = matcher.group(10);
301      b = matcher.group(12);
302    }
303    size += tb != null ? Long.parseLong(tb)*HConstants.TB_IN_BYTES:0;
304    size += gb != null ? Long.parseLong(gb)*HConstants.GB_IN_BYTES:0;
305    size += mb != null ? Long.parseLong(mb)*HConstants.MB_IN_BYTES:0;
306    size += kb != null ? Long.parseLong(kb)*HConstants.KB_IN_BYTES:0;
307    size += b != null ? Long.parseLong(b):0;
308
309    if (expectedSize != null && Long.parseLong(expectedSize) != size) {
310      throw new HBaseException("Malformed size string: values in byte and human readable" +
311        "format do not match");
312    }
313    return size;
314  }
315
316  /**
317   * Pretty prints a collection of any type to a string. Relies on toString() implementation of the
318   * object type.
319   * @param collection collection to pretty print.
320   * @return Pretty printed string for the collection.
321   */
322  public static String toString(Collection<?> collection) {
323    List<String> stringList = new ArrayList<>();
324    for (Object o: collection) {
325      stringList.add(Objects.toString(o));
326    }
327    return "[" + String.join(",", stringList) + "]";
328  }
329
330}