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) nn * @return the value
081   *      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. n * @return value in seconds
159   */
160  private static long humanReadableIntervalToSec(final String humanReadableInterval)
161    throws HBaseException {
162    if (humanReadableInterval == null || humanReadableInterval.equalsIgnoreCase("FOREVER")) {
163      return HConstants.FOREVER;
164    }
165
166    try {
167      return Long.parseLong(humanReadableInterval);
168    } catch (NumberFormatException ex) {
169      LOG.debug("Given interval value is not a number, parsing for human readable format");
170    }
171
172    String days = null;
173    String hours = null;
174    String minutes = null;
175    String seconds = null;
176    String expectedTtl = null;
177    long ttl;
178
179    Matcher matcher = PrettyPrinter.INTERVAL_PATTERN.matcher(humanReadableInterval);
180    if (matcher.matches()) {
181      expectedTtl = matcher.group(2);
182      days = matcher.group(4);
183      hours = matcher.group(6);
184      minutes = matcher.group(8);
185      seconds = matcher.group(10);
186    }
187    ttl = 0;
188    ttl += days != null ? Long.parseLong(days) * HConstants.DAY_IN_SECONDS : 0;
189    ttl += hours != null ? Long.parseLong(hours) * HConstants.HOUR_IN_SECONDS : 0;
190    ttl += minutes != null ? Long.parseLong(minutes) * HConstants.MINUTE_IN_SECONDS : 0;
191    ttl += seconds != null ? Long.parseLong(seconds) : 0;
192
193    if (expectedTtl != null && Long.parseLong(expectedTtl) != ttl) {
194      throw new HBaseException(
195        "Malformed TTL string: TTL values in seconds and human readable" + "format do not match");
196    }
197    return ttl;
198  }
199
200  /**
201   * Convert a long size to a human readable string. Example: 10763632640 -> 10763632640 B (10GB
202   * 25MB)
203   * @param size the size in bytes
204   * @return human readable string
205   */
206  private static String humanReadableByte(final long size) {
207    StringBuilder sb = new StringBuilder();
208    long tb, gb, mb, kb, b;
209
210    if (size < HConstants.KB_IN_BYTES) {
211      sb.append(size);
212      sb.append(" B");
213      return sb.toString();
214    }
215
216    tb = size / HConstants.TB_IN_BYTES;
217    gb = (size - HConstants.TB_IN_BYTES * tb) / HConstants.GB_IN_BYTES;
218    mb =
219      (size - HConstants.TB_IN_BYTES * tb - HConstants.GB_IN_BYTES * gb) / HConstants.MB_IN_BYTES;
220    kb = (size - HConstants.TB_IN_BYTES * tb - HConstants.GB_IN_BYTES * gb
221      - HConstants.MB_IN_BYTES * mb) / HConstants.KB_IN_BYTES;
222    b = (size - HConstants.TB_IN_BYTES * tb - HConstants.GB_IN_BYTES * gb
223      - HConstants.MB_IN_BYTES * mb - HConstants.KB_IN_BYTES * kb);
224
225    sb.append(size).append(" B (");
226    if (tb > 0) {
227      sb.append(tb);
228      sb.append("TB");
229    }
230
231    if (gb > 0) {
232      sb.append(tb > 0 ? " " : "");
233      sb.append(gb);
234      sb.append("GB");
235    }
236
237    if (mb > 0) {
238      sb.append(tb + gb > 0 ? " " : "");
239      sb.append(mb);
240      sb.append("MB");
241    }
242
243    if (kb > 0) {
244      sb.append(tb + gb + mb > 0 ? " " : "");
245      sb.append(kb);
246      sb.append("KB");
247    }
248
249    if (b > 0) {
250      sb.append(tb + gb + mb + kb > 0 ? " " : "");
251      sb.append(b);
252      sb.append("B");
253    }
254
255    sb.append(")");
256    return sb.toString();
257  }
258
259  /**
260   * Convert a human readable size to bytes. Examples of the human readable size are: 50 GB 20 MB 1
261   * KB , 25000 B etc. The units of size specified can be in uppercase as well as lowercase. Also,
262   * if a single number is specified without any time unit, it is assumed to be in bytes.
263   * @param humanReadableSize human readable size
264   * @return value in bytes n
265   */
266  private static long humanReadableSizeToBytes(final String humanReadableSize)
267    throws HBaseException {
268    if (humanReadableSize == null) {
269      return -1;
270    }
271
272    try {
273      return Long.parseLong(humanReadableSize);
274    } catch (NumberFormatException ex) {
275      LOG.debug("Given size value is not a number, parsing for human readable format");
276    }
277
278    String tb = null;
279    String gb = null;
280    String mb = null;
281    String kb = null;
282    String b = null;
283    String expectedSize = null;
284    long size = 0;
285
286    Matcher matcher = PrettyPrinter.SIZE_PATTERN.matcher(humanReadableSize);
287    if (matcher.matches()) {
288      expectedSize = matcher.group(2);
289      tb = matcher.group(4);
290      gb = matcher.group(6);
291      mb = matcher.group(8);
292      kb = matcher.group(10);
293      b = matcher.group(12);
294    }
295    size += tb != null ? Long.parseLong(tb) * HConstants.TB_IN_BYTES : 0;
296    size += gb != null ? Long.parseLong(gb) * HConstants.GB_IN_BYTES : 0;
297    size += mb != null ? Long.parseLong(mb) * HConstants.MB_IN_BYTES : 0;
298    size += kb != null ? Long.parseLong(kb) * HConstants.KB_IN_BYTES : 0;
299    size += b != null ? Long.parseLong(b) : 0;
300
301    if (expectedSize != null && Long.parseLong(expectedSize) != size) {
302      throw new HBaseException(
303        "Malformed size string: values in byte and human readable" + "format do not match");
304    }
305    return size;
306  }
307
308  /**
309   * Pretty prints a collection of any type to a string. Relies on toString() implementation of the
310   * object type.
311   * @param collection collection to pretty print.
312   * @return Pretty printed string for the collection.
313   */
314  public static String toString(Collection<?> collection) {
315    List<String> stringList = new ArrayList<>();
316    for (Object o : collection) {
317      stringList.add(Objects.toString(o));
318    }
319    return "[" + String.join(",", stringList) + "]";
320  }
321
322}