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