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.io.UnsupportedEncodingException;
021import java.net.InetAddress;
022import java.net.URI;
023import java.net.URLDecoder;
024import java.nio.charset.StandardCharsets;
025import java.util.Collections;
026import java.util.Map;
027import java.util.Objects;
028import java.util.stream.Collectors;
029import org.apache.commons.lang3.StringUtils;
030import org.apache.hadoop.conf.Configuration;
031import org.apache.yetus.audience.InterfaceAudience;
032
033import org.apache.hbase.thirdparty.com.google.common.base.Joiner;
034import org.apache.hbase.thirdparty.com.google.common.base.Splitter;
035import org.apache.hbase.thirdparty.com.google.common.net.InetAddresses;
036
037/**
038 * Utility for Strings.
039 */
040@InterfaceAudience.Private
041public final class Strings {
042  public static final String DEFAULT_SEPARATOR = "=";
043  public static final String DEFAULT_KEYVALUE_SEPARATOR = ", ";
044
045  public static final Joiner JOINER = Joiner.on(",");
046  public static final Splitter SPLITTER = Splitter.on(",");
047
048  private Strings() {
049  }
050
051  /**
052   * Append to a StringBuilder a key/value. Uses default separators.
053   * @param sb    StringBuilder to use
054   * @param key   Key to append.
055   * @param value Value to append.
056   * @return Passed <code>sb</code> populated with key/value.
057   */
058  public static StringBuilder appendKeyValue(final StringBuilder sb, final String key,
059    final Object value) {
060    return appendKeyValue(sb, key, value, DEFAULT_SEPARATOR, DEFAULT_KEYVALUE_SEPARATOR);
061  }
062
063  /**
064   * Append to a StringBuilder a key/value. Uses default separators.
065   * @param sb                StringBuilder to use
066   * @param key               Key to append.
067   * @param value             Value to append.
068   * @param separator         Value to use between key and value.
069   * @param keyValueSeparator Value to use between key/value sets.
070   * @return Passed <code>sb</code> populated with key/value.
071   */
072  public static StringBuilder appendKeyValue(final StringBuilder sb, final String key,
073    final Object value, final String separator, final String keyValueSeparator) {
074    if (sb.length() > 0) {
075      sb.append(keyValueSeparator);
076    }
077    return sb.append(key).append(separator).append(value);
078  }
079
080  /**
081   * Given a PTR string generated via reverse DNS lookup, return everything except the trailing
082   * period. Example for host.example.com., return host.example.com
083   * @param dnPtr a domain name pointer (PTR) string.
084   * @return Sanitized hostname with last period stripped off.
085   */
086  public static String domainNamePointerToHostName(String dnPtr) {
087    if (dnPtr == null) {
088      return null;
089    }
090
091    return dnPtr.endsWith(".") ? dnPtr.substring(0, dnPtr.length() - 1) : dnPtr;
092  }
093
094  /**
095   * Returns whether the given string is an IP address, including bracketed IPv6 URI form.
096   * @param host hostname or IP
097   * @return {@code true} if {@code host} is an IP address
098   * @throws NullPointerException if {@code host} is {@code null}
099   */
100  public static boolean isInetAddress(String host) {
101    Objects.requireNonNull(host, "Hostname or IP cannot be null");
102    host = host.trim();
103    if (host.startsWith("[") && host.endsWith("]")) {
104      return InetAddresses.isUriInetAddress(host);
105    }
106    return InetAddresses.isInetAddress(host);
107  }
108
109  private static InetAddress parseInetAddress(String host) {
110    host = host.trim();
111    if (host.startsWith("[") && host.endsWith("]")) {
112      return InetAddresses.forUriString(host);
113    }
114    return InetAddresses.forString(host);
115  }
116
117  /**
118   * Compare two host identifiers for equality. DNS hostnames are compared case-insensitively
119   * because DNS labels are case-insensitive. IP address literals are compared by numeric address.
120   * @param left  first hostname or IP
121   * @param right second hostname or IP
122   * @return {@code true} if both refer to the same host identifier
123   * @throws NullPointerException if either argument is {@code null}
124   */
125  public static boolean hostnamesEqual(String left, String right) {
126    Objects.requireNonNull(left, "Hostname or IP cannot be null");
127    Objects.requireNonNull(right, "Hostname or IP cannot be null");
128    boolean leftIsIp = isInetAddress(left);
129    boolean rightIsIp = isInetAddress(right);
130    if (leftIsIp != rightIsIp) {
131      return false;
132    }
133    if (leftIsIp) {
134      return parseInetAddress(left).equals(parseInetAddress(right));
135    }
136    return left.equalsIgnoreCase(right);
137  }
138
139  /**
140   * Push the input string to the right by appending a character before it, usually a space.
141   * @param input   the string to pad
142   * @param padding the character to repeat to the left of the input string
143   * @param length  the desired total length including the padding
144   * @return padding characters + input
145   */
146  public static String padFront(String input, char padding, int length) {
147    if (input.length() > length) {
148      throw new IllegalArgumentException("input \"" + input + "\" longer than maxLength=" + length);
149    }
150    int numPaddingCharacters = length - input.length();
151    return StringUtils.repeat(padding, numPaddingCharacters) + input;
152  }
153
154  /**
155   * Parse the query string of an URI to a key value map. If a single key occurred multiple times,
156   * only the first one will take effect.
157   */
158  public static Map<String, String> parseURIQueries(URI uri) {
159    if (StringUtils.isBlank(uri.getRawQuery())) {
160      return Collections.emptyMap();
161    }
162    return Splitter.on('&').trimResults().splitToStream(uri.getRawQuery()).map(kv -> {
163      int idx = kv.indexOf('=');
164      try {
165        if (idx > 0) {
166          return Pair.newPair(
167            URLDecoder.decode(kv.substring(0, idx), StandardCharsets.UTF_8.name()),
168            URLDecoder.decode(kv.substring(idx + 1), StandardCharsets.UTF_8.name()));
169        } else {
170          return Pair.newPair(URLDecoder.decode(kv, StandardCharsets.UTF_8.name()), "");
171        }
172      } catch (UnsupportedEncodingException e) {
173        // should not happen
174        throw new AssertionError(e);
175      }
176    }).collect(Collectors.toMap(Pair::getFirst, Pair::getSecond, (v1, v2) -> v1));
177  }
178
179  /**
180   * Apply the key value pairs in the query string of the given URI to the given Configuration. If a
181   * single key occurred multiple times, only the first one will take effect.
182   */
183  public static void applyURIQueriesToConf(URI uri, Configuration conf) {
184    parseURIQueries(uri).forEach(conf::set);
185  }
186
187  /**
188   * Note: This method was taken from org.apache.hadoop.util.StringUtils.humanReadableInt(long).
189   * Reason: that method got deprecated and this method provides an easy-to-understand usage of
190   * StringUtils.TraditionalBinaryPrefix.long2String. Given an integer, return a string that is in
191   * an approximate, but human readable format.
192   * @param number the number to format
193   * @return a human readable form of the integer
194   */
195  public static String humanReadableInt(long number) {
196    return org.apache.hadoop.util.StringUtils.TraditionalBinaryPrefix.long2String(number, "", 1);
197  }
198}