/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.tajo.util; import com.google.common.base.Function; import com.google.common.collect.Iterables; import io.netty.util.CharsetUtil; import org.apache.commons.lang.CharUtils; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.SystemUtils; import org.apache.hadoop.util.Shell; import org.apache.hadoop.util.ShutdownHookManager; import org.apache.hadoop.util.SignalLogger; import org.apache.tajo.storage.StorageConstants; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; import java.util.Arrays; import java.util.BitSet; import java.util.Iterator; public class StringUtils { /** * Priority of the StringUtils shutdown hook. */ public static final int SHUTDOWN_HOOK_PRIORITY = 0; /** * * Given the time in long milliseconds, returns a * String in the format X hrs, Y mins, S sec, M msecs * * @param timeDiff The time difference to format */ public static String formatTime(long timeDiff){ StringBuilder buf = new StringBuilder(); long hours = timeDiff / (60*60*1000); long rem = (timeDiff % (60*60*1000)); long minutes = rem / (60*1000); rem = rem % (60*1000); long seconds = rem / 1000; if (hours != 0){ buf.append(hours); buf.append(" hrs, "); } if (minutes != 0){ buf.append(minutes); buf.append(" mins, "); } if (seconds != 0) { buf.append(seconds); buf.append(" sec"); } if (timeDiff < 1000) { buf.append(timeDiff); buf.append(" msec"); } return buf.toString(); } /** * Check Seven-bit ASCII */ public static boolean isPureAscii(String v) { // get thread-safe encoder CharsetEncoder asciiEncoder = CharsetUtil.getEncoder(CharsetUtil.US_ASCII); return asciiEncoder.canEncode(v); } public static String quote(String str) { return "'" + str + "'"; } public static String doubleQuote(String str) { return "\"" + str + "\""; } public static boolean isPartOfAnsiSQLIdentifier(char character) { return isLowerCaseAlphabet(character) || isUpperCaseAlphabet(character) || isDigit(character) || isUndersscore(character); } public static boolean isUndersscore(char character) { return character == '_'; } public static boolean isLowerCaseAlphabet(char character) { return 'a' <= character && character <= 'z'; } public static boolean isUpperCaseAlphabet(char character) { return 'A' <= character && character <= 'Z'; } public static boolean isDigit(char character) { return '0' <= character && character <= '9'; } private static final String REGEX_SPECIAL_CHARACTERS = "([.*${}?|\\^\\-\\[\\]])"; public static String escapeRegexp(String literal) { return literal.replaceAll(REGEX_SPECIAL_CHARACTERS, "\\\\$1"); } private static final String LIKE_SPECIAL_CHARACTERS = "([_%])"; public static String escapeLike(String literal) { return literal.replaceAll(LIKE_SPECIAL_CHARACTERS, "\\\\$1"); } /** * Return a message for logging. * @param prefix prefix keyword for the message * @param msg content of the message * @return a message for logging */ private static String toStartupShutdownString(String prefix, String [] msg) { StringBuilder b = new StringBuilder(prefix); b.append("\n/************************************************************"); for(String s : msg) b.append("\n" + prefix + s); b.append("\n************************************************************/"); return b.toString(); } /** * Print a log message for starting up and shutting down * @param clazz the class of the server * @param args arguments * @param LOG the target log object */ public static void startupShutdownMessage(Class<?> clazz, String[] args, final org.apache.commons.logging.Log LOG) { final String hostname = org.apache.hadoop.net.NetUtils.getHostname(); final String classname = clazz.getSimpleName(); LOG.info( toStartupShutdownString("STARTUP_MSG: ", new String[] { "Starting " + classname, " host = " + hostname, " args = " + Arrays.asList(args), " version = " + org.apache.tajo.util.VersionInfo.getVersion(), " classpath = " + System.getProperty("java.class.path"), " build = " + org.apache.tajo.util.VersionInfo.getUrl() + " -r " + org.apache.tajo.util.VersionInfo.getRevision() + "; compiled by '" + org.apache.tajo.util.VersionInfo.getUser() + "' on " + org.apache.tajo.util.VersionInfo.getDate(), " java = " + System.getProperty("java.version") } ) ); if (SystemUtils.IS_OS_UNIX) { try { SignalLogger.INSTANCE.register(LOG); } catch (Throwable t) { LOG.warn("failed to register any UNIX signal loggers: ", t); } } ShutdownHookManager.get().addShutdownHook( new Runnable() { @Override public void run() { LOG.info(toStartupShutdownString("SHUTDOWN_MSG: ", new String[]{ "Shutting down " + classname + " at " + hostname})); } }, SHUTDOWN_HOOK_PRIORITY); } public static String unicodeEscapedDelimiter(String value) { try { String delimiter = StringEscapeUtils.unescapeJava(value); StringBuilder builder = new StringBuilder(); for (char achar : delimiter.toCharArray()) { builder.append(unicodeEscapedDelimiter(achar)); } return builder.toString(); } catch (Throwable e) { } return value; } public static String unicodeEscapedDelimiter(char c) { return CharUtils.unicodeEscaped(c); } /** * The following lines of code that deals with escape characters is mostly copied from HIVE's FileUtils.java */ static BitSet charToEscape = new BitSet(128); static { for (char c = 0; c < ' '; c++) { charToEscape.set(c); } /** * ASCII 01-1F are HTTP control characters that need to be escaped. * \u000A and \u000D are \n and \r, respectively. */ char[] clist = new char[] {'\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\u0007', '\u0008', '\u0009', '\n', '\u000B', '\u000C', '\r', '\u000E', '\u000F', '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001A', '\u001B', '\u001C', '\u001D', '\u001E', '\u001F', '"', '#', '%', '\'', '*', '/', ':', '=', '?', '\\', '\u007F', '{', '[', ']', '^'}; for (char c : clist) { charToEscape.set(c); } if(Shell.WINDOWS){ // On windows, following chars need to be escaped as well char [] winClist = {' ', '<','>','|'}; for (char c : winClist) { charToEscape.set(c); } } } static boolean needsEscaping(char c) { return c >= 0 && c < charToEscape.size() && charToEscape.get(c); } public static String escapePathName(String path) { return escapePathName(path, null); } /** * Escapes a path name. * @param path The path to escape. * @param defaultPath * The default name for the path, if the given path is empty or null. * @return An escaped path name. */ public static String escapePathName(String path, String defaultPath) { if (path == null || path.length() == 0) { if (defaultPath == null) { return StorageConstants.DEFAULT_PARTITION_NAME; } else { return defaultPath; } } StringBuilder sb = new StringBuilder(); for (int i = 0; i < path.length(); i++) { char c = path.charAt(i); if (needsEscaping(c)) { sb.append('%'); sb.append(String.format("%1$02X", (int) c)); } else { sb.append(c); } } return sb.toString(); } public static String unescapePathName(String path) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < path.length(); i++) { char c = path.charAt(i); if (c == '%' && i + 2 < path.length()) { int code = -1; try { code = Integer.valueOf(path.substring(i + 1, i + 3), 16); } catch (Exception e) { code = -1; } if (code >= 0) { sb.append((char) code); i += 2; continue; } } sb.append(c); } return sb.toString(); } public static char[][] padChars(char []...bytes) { char[] startChars = bytes[0]; char[] endChars = bytes[1]; char[][] padded = new char[2][]; int max = Math.max(startChars.length, endChars.length); padded[0] = new char[max]; padded[1] = new char[max]; System.arraycopy(startChars, 0, padded[0], 0, startChars.length); for (int i = startChars.length; i < max; i++) { padded[0][i] = 0; } System.arraycopy(endChars, 0, padded[1], 0, endChars.length); for (int i = endChars.length; i < max; i++) { padded[1][i] = 0; } return padded; } public static char[] convertBytesToChars(byte[] src, Charset charset) { CharsetDecoder decoder = charset.newDecoder(); char[] resultArray = new char[(int) (src.length * decoder.maxCharsPerByte())]; if (src.length != 0) { ByteBuffer byteBuffer = ByteBuffer.wrap(src); CharBuffer charBuffer = CharBuffer.wrap(resultArray); decoder.onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter(CodingErrorAction.REPLACE); decoder.reset(); CoderResult coderResult = decoder.decode(byteBuffer, charBuffer, true); if (coderResult.isUnderflow()) { coderResult = decoder.flush(charBuffer); if (coderResult.isUnderflow()) { if (resultArray.length != charBuffer.position()) { resultArray = Arrays.copyOf(resultArray, charBuffer.position()); } } } } return resultArray; } public static byte[] convertCharsToBytes(char[] src, Charset charset) { CharsetEncoder encoder = charset.newEncoder(); byte[] resultArray = new byte[(int) (src.length * encoder.maxBytesPerChar())]; if (src.length != 0) { CharBuffer charBuffer = CharBuffer.wrap(src); ByteBuffer byteBuffer = ByteBuffer.wrap(resultArray); encoder.onMalformedInput(CodingErrorAction.REPLACE).onUnmappableCharacter(CodingErrorAction.REPLACE); encoder.reset(); CoderResult coderResult = encoder.encode(charBuffer, byteBuffer, true); if (coderResult.isUnderflow()) { coderResult = encoder.flush(byteBuffer); if (coderResult.isUnderflow()) { if (resultArray.length != byteBuffer.position()) { resultArray = Arrays.copyOf(resultArray, byteBuffer.position()); } } } } return resultArray; } /** * Concatenate all objects' string with the delimiter ", " * * @param objects Iterable objects * @return A joined string */ public static String join(Iterable objects) { return join(objects, ", "); } /** * Concatenate all objects' string with a delimiter string * * @param objects Iterable objects * @param delimiter Delimiter string * @return A joined string */ public static String join(Iterable objects, String delimiter) { return join(objects, delimiter, 0, Iterables.size(objects)); } /** * Concatenate all objects' string with a delimiter string * * @param objects object Iterable * @param delimiter Delimiter string * @param startIndex the begin index to join * @return A joined string */ public static String join(Iterable objects, String delimiter, int startIndex) { return join(objects, delimiter, startIndex, Iterables.size(objects)); } /** * Concatenate all objects' string with a delimiter string * * @param objects object Iterable * @param delimiter Delimiter string * @param startIndex the begin index to join * @param length the number of columns to be joined * @return A joined string */ public static String join(Iterable objects, String delimiter, int startIndex, int length) { return join(objects, delimiter, startIndex, length, new Function<Object, String>() { @Override public String apply(Object input) { return input.toString(); } }); } /** * Concatenate all objects' string with a delimiter string * * @param objects object Iterable * @param delimiter Delimiter string * @param f convert from a type to string * @return A joined string */ public static <T> String join(Iterable<T> objects, String delimiter, Function<T, String> f) { return join(objects, delimiter, 0, Iterables.size(objects), f); } public static <T> String join(Iterable<T> objects, String delimiter, int startIndex, int length, Function<T, String> f) { boolean first = true; StringBuilder sb = new StringBuilder(); int endIndex = startIndex + length; Iterator itr = objects.iterator(); for(int i = 0; i < endIndex; i++) { if(i < startIndex) { itr.next(); } else { if (first) { first = false; } else { sb.append(delimiter); } sb.append(f.apply((T) itr.next())); } } return sb.toString(); } /** * Concatenate all objects' string with the delimiter ", " * * @param objects Iterable objects * @return A joined string */ public static String join(Object[] objects) { return join(objects, ", "); } /** * Concatenate all objects' string with the delimiter ", " * * @param objects Iterable objects * @return A joined string */ public static String join(Object[] objects, String delimiter) { return join(objects, delimiter, 0, objects.length); } /** * Concatenate all objects' string with a delimiter string * * @param objects object array * @param delimiter Delimiter string * @param startIndex the begin index to join * @return A joined string */ public static String join(Object[] objects, String delimiter, int startIndex) { return join(objects, delimiter, startIndex, objects.length); } /** * Concatenate all objects' string with a delimiter string * * @param objects object array * @param delimiter Delimiter string * @param startIndex the begin index to join * @param length the number of columns to be joined * @return A joined string */ public static String join(Object[] objects, String delimiter, int startIndex, int length) { return join(objects, delimiter, startIndex, length, new Function<Object, String>() { @Override public String apply(Object input) { return input.toString(); } }); } /** * Concatenate all objects' string with a delimiter string * * @param objects object array * @param delimiter Delimiter string * @param f convert from a type to string * @return A joined string */ public static <T> String join(T [] objects, String delimiter, Function<T, String> f) { return join(objects, delimiter, 0, objects.length, f); } public static <T> String join(T [] objects, String delimiter, int startIndex, int length, Function<T, String> f) { boolean first = true; StringBuilder sb = new StringBuilder(); int endIndex = startIndex + length; for(int i = startIndex; i + startIndex < endIndex; i++) { if (first) { first = false; } else { sb.append(delimiter); } sb.append(f.apply(objects[i])); } return sb.toString(); } /** * <p>Checks if a String is empty ("") or null.</p> * * <pre> * StringUtils.isEmpty(null) = true * StringUtils.isEmpty("") = true * StringUtils.isEmpty(" ") = false * StringUtils.isEmpty("bob") = false * StringUtils.isEmpty(" bob ") = false * </pre> * * <p>NOTE: This method changed in Lang version 2.0. * It no longer trims the String. * That functionality is available in isBlank().</p> * * @param str the String to check, may be null * @return <code>true</code> if the String is empty or null */ public static boolean isEmpty(String str) { return str == null || str.length() == 0; } }