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
045  public enum Unit {
046    TIME_INTERVAL,
047    LONG,
048    BOOLEAN,
049    NONE
050  }
051
052  public static String format(final String value, final Unit unit) {
053    StringBuilder human = new StringBuilder();
054    switch (unit) {
055      case TIME_INTERVAL:
056        human.append(humanReadableTTL(Long.parseLong(value)));
057        break;
058      case LONG:
059        byte[] longBytes = Bytes.toBytesBinary(value);
060        human.append(String.valueOf(Bytes.toLong(longBytes)));
061        break;
062      case BOOLEAN:
063        byte[] booleanBytes = Bytes.toBytesBinary(value);
064        human.append(String.valueOf(Bytes.toBoolean(booleanBytes)));
065        break;
066      default:
067        human.append(value);
068    }
069    return human.toString();
070  }
071
072  /**
073   * Convert a human readable string to its value.
074   * @see org.apache.hadoop.hbase.util.PrettyPrinter#format(String, Unit)
075   * @param pretty
076   * @param unit
077   * @return the value corresponding to the human readable string
078   */
079  public static String valueOf(final String pretty, final Unit unit) throws HBaseException {
080    StringBuilder value = new StringBuilder();
081    switch (unit) {
082      case TIME_INTERVAL:
083        value.append(humanReadableIntervalToSec(pretty));
084        break;
085      default:
086        value.append(pretty);
087    }
088    return value.toString();
089  }
090
091  @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="ICAST_INTEGER_MULTIPLY_CAST_TO_LONG",
092      justification="Will not overflow")
093  private static String humanReadableTTL(final long interval){
094    StringBuilder sb = new StringBuilder();
095    int days, hours, minutes, seconds;
096
097    // edge cases first
098    if (interval == Integer.MAX_VALUE) {
099      sb.append("FOREVER");
100      return sb.toString();
101    }
102    if (interval < HConstants.MINUTE_IN_SECONDS) {
103      sb.append(interval);
104      sb.append(" SECOND").append(interval == 1 ? "" : "S");
105      return sb.toString();
106    }
107
108    days  =   (int) (interval / HConstants.DAY_IN_SECONDS);
109    hours =   (int) (interval - HConstants.DAY_IN_SECONDS * days) / HConstants.HOUR_IN_SECONDS;
110    minutes = (int) (interval - HConstants.DAY_IN_SECONDS * days
111        - HConstants.HOUR_IN_SECONDS * hours) / HConstants.MINUTE_IN_SECONDS;
112    seconds = (int) (interval - HConstants.DAY_IN_SECONDS * days
113        - HConstants.HOUR_IN_SECONDS * hours - HConstants.MINUTE_IN_SECONDS * minutes);
114
115    sb.append(interval);
116    sb.append(" SECONDS (");
117
118    if (days > 0) {
119      sb.append(days);
120      sb.append(" DAY").append(days == 1 ? "" : "S");
121    }
122
123    if (hours > 0) {
124      sb.append(days > 0 ? " " : "");
125      sb.append(hours);
126      sb.append(" HOUR").append(hours == 1 ? "" : "S");
127    }
128
129    if (minutes > 0) {
130      sb.append(days + hours > 0 ? " " : "");
131      sb.append(minutes);
132      sb.append(" MINUTE").append(minutes == 1 ? "" : "S");
133    }
134
135    if (seconds > 0) {
136      sb.append(days + hours + minutes > 0 ? " " : "");
137      sb.append(seconds);
138      sb.append(" SECOND").append(minutes == 1 ? "" : "S");
139    }
140
141    sb.append(")");
142
143    return sb.toString();
144  }
145
146  /**
147   * Convert a human readable time interval to seconds. Examples of the human readable
148   * time intervals are: 50 DAYS 1 HOUR 30 MINUTES , 25000 SECONDS etc.
149   * The units of time specified can be in uppercase as well as lowercase. Also, if a
150   * single number is specified without any time unit, it is assumed to be in seconds.
151   * @param humanReadableInterval
152   * @return value in seconds
153   */
154  private static long humanReadableIntervalToSec(final String humanReadableInterval)
155          throws HBaseException {
156    if (humanReadableInterval == null || humanReadableInterval.equalsIgnoreCase("FOREVER")) {
157      return HConstants.FOREVER;
158    }
159
160    try {
161      return Long.parseLong(humanReadableInterval);
162    } catch(NumberFormatException ex) {
163      LOG.debug("Given interval value is not a number, parsing for human readable format");
164    }
165
166    String days = null;
167    String hours = null;
168    String minutes = null;
169    String seconds = null;
170    String expectedTtl = null;
171    long ttl;
172
173    Matcher matcher = PrettyPrinter.INTERVAL_PATTERN.matcher(humanReadableInterval);
174    if (matcher.matches()) {
175      expectedTtl = matcher.group(2);
176      days = matcher.group(4);
177      hours = matcher.group(6);
178      minutes = matcher.group(8);
179      seconds = matcher.group(10);
180    }
181    ttl = 0;
182    ttl += days != null ? Long.parseLong(days)*HConstants.DAY_IN_SECONDS:0;
183    ttl += hours != null ? Long.parseLong(hours)*HConstants.HOUR_IN_SECONDS:0;
184    ttl += minutes != null ? Long.parseLong(minutes)*HConstants.MINUTE_IN_SECONDS:0;
185    ttl += seconds != null ? Long.parseLong(seconds):0;
186
187    if (expectedTtl != null && Long.parseLong(expectedTtl) != ttl) {
188      throw new HBaseException("Malformed TTL string: TTL values in seconds and human readable" +
189              "format do not match");
190    }
191    return ttl;
192  }
193
194  /**
195   * Pretty prints a collection of any type to a string. Relies on toString() implementation of the
196   * object type.
197   * @param collection collection to pretty print.
198   * @return Pretty printed string for the collection.
199   */
200  public static String toString(Collection<?> collection) {
201    List<String> stringList = new ArrayList<>();
202    for (Object o: collection) {
203      stringList.add(Objects.toString(o));
204    }
205    return "[" + String.join(",", stringList) + "]";
206  }
207
208}