/*************************************************************************** * Copyright (C) 2011 by H-Store Project * * Brown University * * Massachusetts Institute of Technology * * Yale University * * * * http://hstore.cs.brown.edu/ * * * * Permission is hereby granted, free of charge, to any person obtaining * * a copy of this software and associated documentation files (the * * "Software"), to deal in the Software without restriction, including * * without limitation the rights to use, copy, modify, merge, publish, * * distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to * * the following conditions: * * * * The above copyright notice and this permission notice shall be * * included in all copies or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * * OTHER DEALINGS IN THE SOFTWARE. * ***************************************************************************/ package edu.brown.utils; import java.io.PrintWriter; import java.io.StringWriter; import java.math.BigInteger; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.DecimalFormat; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import org.voltdb.types.TimestampType; import com.google.protobuf.ByteString; /** * @author pavlo */ public abstract class StringUtil { public static final String SPACER = " "; public static final String DOUBLE_LINE = StringUtil.repeat("=", 64) + "\n"; public static final String SINGLE_LINE = StringUtil.repeat("-", 64) + "\n"; private static final Pattern LINE_SPLIT = Pattern.compile("\n"); private static final Pattern LIST_SPLIT = Pattern.compile("[ ]*,"); private static final Pattern TITLE_SPLIT = Pattern.compile(" "); public static final String SET_PLAIN_TEXT = "\033[0;0m"; public static final String SET_BOLD_TEXT = "\033[0;1m"; private static final float BASE = 1024, KB = BASE, MB = KB * BASE, GB = MB * BASE; private static final DecimalFormat df = new DecimalFormat("#.##"); private static final String HEADER_MARKER = "-"; private static final int HEADER_LENGTH = 80; /** Unicode Arrow Characters */ public static final String UNICODE_UP_ARROW = "\u25B2"; public static final String UNICODE_DOWN_ARROW = "\u25BC"; public static final String UNICODE_RIGHT_ARROW = "\u2192"; /** * Return the given Exception's stacktrace as a string * @param ex */ public static String stacktrace(Throwable ex) { StringWriter writer = new StringWriter(); ex.printStackTrace(new PrintWriter(writer)); return (writer.toString()); } /** * Wrap the given string with the control characters * to make the text appear bold in the console */ public static String bold(String str) { return (SET_BOLD_TEXT + str + SET_PLAIN_TEXT); } /** * http://ubuntuforums.org/showpost.php?p=10215516&postcount=5 * @param bytes * @return */ public static String formatSize(long bytes) { if (bytes >= GB) { return df.format(bytes / GB) + " GB"; } else if (bytes >= MB) { return df.format(bytes / MB) + " MB"; } else if (bytes >= KB) { return df.format(bytes / KB) + " KB"; } return "" + (int) bytes + " bytes"; } public static String formatTime(String format, double nanoseconds) { return formatTime(format, nanoseconds, TimeUnit.NANOSECONDS); } /** * * @param format * @param nanoseconds * @return */ public static String formatTime(String format, double time, TimeUnit unit) { double nanoseconds; nanoseconds = Math.abs(unit.toNanos((long)time)); String unitLabel = "ns"; // Seconds if (nanoseconds > 1000000000) { time /= 1000000000d; unitLabel = "s"; // Milliseconds } else if (nanoseconds > 1000000) { time /= 1000000d; unitLabel = "ms"; // Microseconds } else if (nanoseconds > 1000) { time /= 1000d; unitLabel = "\u00B5s"; } return String.format(format, (double)time) + unitLabel; } /** * Split the given string into based on newlines */ public static String[] splitLines(String str) { return (str != null ? LINE_SPLIT.split(str) : null); } /** * Split the given string into based on list delimiters * This will strip out any whitespace */ public static String[] splitList(String str) { return (str != null ? LIST_SPLIT.split(str) : null); } public static String header(String msg) { return StringUtil.header(msg, HEADER_MARKER, HEADER_LENGTH); } public static String header(String msg, int length) { return StringUtil.header(msg, HEADER_MARKER, length); } public static String header(String msg, String marker) { return StringUtil.header(msg, marker, HEADER_LENGTH); } /** * Create a nicely format header string where the given message is surround * on both sides by the marker. Example: "---------- MSG ----------" * * @param msg * @param marker * @param length * @return */ public static String header(String msg, String marker, int length) { int msg_length = msg.length(); length = Math.max(msg_length, length); int border_len = (length - msg_length - 2) / 2; String border = StringUtil.repeat(marker, border_len); boolean add_extra = (border_len + msg_length + 2 + 1 == length); return String.format("%s %s %s%s", border, msg, border, (add_extra ? marker : "")); } /** * Return the MD5 checksum of the given string * * @param input * @return */ public static String md5sum(String input) { return md5sum(input.getBytes()); } /** * Return the MD5 checksum of the given ByteBuffer * @param input * @return */ public static String md5sum(ByteBuffer bytes) { byte b[] = new byte[bytes.limit()]; bytes.get(b); return md5sum(b); } /** * Return the MD5 checksum of the given byte array * @param input * @return */ public static String md5sum(byte bytes[]) { MessageDigest digest = null; try { digest = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException ex) { throw new RuntimeException("Unable to compute md5sum for string", ex); } assert (digest != null); digest.update(bytes); BigInteger hash = new BigInteger(1, digest.digest()); return (hash.toString(16)); } /** * Split the multi-lined strings into separate columns * @param strs * @return */ public static String columns(Collection<String> strs) { return StringUtil.columns(strs.toArray(new String[0])); } /** * Split the multi-lined strings into separate columns * @param strs * @return */ public static String columns(String... strs) { String lines[][] = new String[strs.length][]; String prefixes[] = new String[strs.length]; int max_length = 0; int max_lines = 0; for (int i = 0; i < strs.length; i++) { if (strs[i] == null) { lines[i] = new String[0]; } else { lines[i] = LINE_SPLIT.split(strs[i]); } prefixes[i] = (i == 0 ? "" : " \u2503 "); for (String line : lines[i]) { max_length = Math.max(max_length, line.length()); } // FOR max_lines = Math.max(max_lines, lines[i].length); } // FOR String f = "%-" + max_length + "s"; StringBuilder sb = new StringBuilder(); for (int ii = 0; ii < max_lines; ii++) { for (int i = 0; i < strs.length; i++) { String line = (ii >= lines[i].length ? "" : lines[i][ii]); sb.append(prefixes[i]).append(String.format(f, line)); } // FOR sb.append("\n"); } // FOR return (sb.toString()); } /** * For a given array of multi-lined strings, return the max width * of all of the lines. * @param strs * @return */ public static int maxWidth(String...strs) { int max_length = 0; String lines[] = null; for (int i = 0; i < strs.length; i++) { if (strs[i] == null) { lines = new String[0]; } else { lines = LINE_SPLIT.split(strs[i]); } for (String line : lines) { max_length = Math.max(max_length, line.length()); } // FOR } // FOR return (max_length); } /** * Return key/value maps into a nicely formatted table Delimiter ":", No * UpperCase Keys, No Boxing * * @param maps * @return */ public static String formatMaps(Map<?, ?>... maps) { return (formatMaps(":", false, false, false, false, true, true, maps)); } /** * Return key/value maps into a nicely formatted table using the given * delimiter No Uppercase Keys, No Boxing * * @param delimiter * @param maps * @return */ public static String formatMaps(String delimiter, Map<?, ?>... maps) { return (formatMaps(delimiter, false, false, false, false, true, true, maps)); } /** * Return key/value maps into a nicely formatted table The maps are * displayed in order from first to last, and there will be a spacer created * between each map. The format for each record is: * <KEY><DELIMITER><SPACING><VALUE> If the delimiter is an equal sign, then * the format is: <KEY><SPACING><DELIMITER><VALUE> * * @param delimiter * @param upper * Upper-case all keys * @param box * Box results * @param border_top * TODO * @param border_bottom * TODO * @param recursive * TODO * @param first_element_title * TODO * @param maps * @return */ @SuppressWarnings("unchecked") public static String formatMaps(String delimiter, boolean upper, boolean box, boolean border_top, boolean border_bottom, boolean recursive, boolean first_element_title, Map<?, ?>... maps) { boolean need_divider = (maps.length > 1 || border_bottom || border_top); // Figure out the largest key size so we can get spacing right int max_key_size = 0; int max_title_size = 0; final Map<Object, String[]> map_keys[] = (Map<Object, String[]>[]) new Map[maps.length]; final boolean map_titles[] = new boolean[maps.length]; for (int i = 0; i < maps.length; i++) { Map<?, ?> m = maps[i]; if (m == null) continue; Map<Object, String[]> keys = new HashMap<Object, String[]>(); boolean first = true; for (Object k : m.keySet()) { String k_str[] = LINE_SPLIT.split(k != null ? k.toString() : ""); keys.put(k, k_str); // If the first element has a null value, then we can let it be // the title for this map // It's length doesn't affect the other keys, but will affect // the total size of the map if (first && first_element_title && m.get(k) == null) { for (String line : k_str) { max_title_size = Math.max(max_title_size, line.length()); } // FOR map_titles[i] = true; } else { for (String line : k_str) { max_key_size = Math.max(max_key_size, line.length()); } // FOR if (first) map_titles[i] = false; } first = false; } // FOR map_keys[i] = keys; } // FOR boolean equalsDelimiter = delimiter.equals("="); final String f = "%-" + (max_key_size + delimiter.length() + 1) + "s" + (equalsDelimiter ? "= " : "") + "%s\n"; // Now make StringBuilder blocks for each map // We do it in this way so that we can get the max length of the values int max_value_size = 0; StringBuilder blocks[] = new StringBuilder[maps.length]; for (int map_i = 0; map_i < maps.length; map_i++) { blocks[map_i] = new StringBuilder(); Map<?, ?> m = maps[map_i]; if (m == null) continue; Map<Object, String[]> keys = map_keys[map_i]; boolean first = true; for (Entry<?, ?> e : m.entrySet()) { String key[] = keys.get(e.getKey()); if (first && map_titles[map_i]) { blocks[map_i].append(StringUtil.join("\n", key)); if (CollectionUtil.last(key).endsWith("\n") == false) blocks[map_i].append("\n"); } else { Object v_obj = e.getValue(); String v = null; if (recursive && v_obj instanceof Map<?, ?>) { v = formatMaps(delimiter, upper, box, border_top, border_bottom, recursive, first_element_title, (Map<?, ?>) v_obj).trim(); } else if (key.length == 1 && key[0].trim().isEmpty() && v_obj == null) { blocks[map_i].append("\n"); continue; } else if (v_obj == null) { v = "null"; } else { v = v_obj.toString(); } // If the key or value is multiple lines, format them // nicely! String value[] = LINE_SPLIT.split(v); int total_lines = Math.max(key.length, value.length); for (int line_i = 0; line_i < total_lines; line_i++) { String k_line = (line_i < key.length ? key[line_i] : ""); if (upper) k_line = k_line.toUpperCase(); String v_line = (line_i < value.length ? value[line_i] : ""); if (line_i == (key.length - 1) && (first == false || (first && v_line.isEmpty() == false))) { if (equalsDelimiter == false && k_line.trim().isEmpty() == false) k_line += ":"; } blocks[map_i].append(String.format(f, k_line, v_line)); if (need_divider) max_value_size = Math.max(max_value_size, v_line.length()); } // FOR if (v.endsWith("\n")) blocks[map_i].append("\n"); } first = false; } } // FOR // Put it all together! // System.err.println("max_title_size=" + max_title_size + // ", max_key_size=" + max_key_size + ", max_value_size=" + // max_value_size + ", delimiter=" + delimiter.length()); int total_width = Math.max(max_title_size, (max_key_size + max_value_size + delimiter.length())) + 1; String dividing_line = (need_divider ? repeat("-", total_width) : ""); StringBuilder sb = null; if (maps.length == 1) { sb = blocks[0]; } else { sb = new StringBuilder(); for (int i = 0; i < maps.length; i++) { if (blocks[i].length() == 0) continue; if (i != 0 && maps[i].size() > 0) sb.append(dividing_line).append("\n"); sb.append(blocks[i]); } // FOR } return (box ? StringBoxUtil.box(sb.toString()) : (border_top ? dividing_line + "\n" : "") + sb.toString() + (border_bottom ? dividing_line : "")); } /** * @param maps * @return */ public static String formatMapsBoxed(Map<?, ?>... maps) { return (formatMaps(":", false, true, false, false, true, true, maps)); } /** * Returns the given string repeated the given # of times * * @param str * @param size * @return */ public static String repeat(String str, int size) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < size; i++) sb.append(str); return (sb.toString()); } /** * Append the prefix to the beginning of each line in str * * @param str * @param prefix * @return */ public static String prefix(String str, String prefix) { String lines[] = LINE_SPLIT.split(str); if (lines.length == 0) return (""); StringBuilder sb = new StringBuilder(); for (int i = 0; i < lines.length; i++) { if (i > 0) sb.append("\n"); sb.append(prefix).append(lines[i]); } // FOR return (sb.toString()); } /** * Abbreviate the given string. The last three chars will be periods * * @param str * @param max * @return */ public static String abbrv(String str, int max) { return (abbrv(str, max, true)); } /** * Abbreviate the given string. If dots, then the last three chars will be * periods * * @param str * @param max * @param dots * @return */ public static String abbrv(String str, int max, boolean dots) { int len = str.length(); String ret = null; if (len > max) { ret = (dots ? str.substring(0, max - 3) + "..." : str.substring(0, max)); } else { ret = str; } return (ret); } /** * Converts a string to title case (ala Python) * * @param string * @return */ public static String title(String string) { return (StringUtil.title(string, false)); } /** * Converts a string to title case (ala Python) * @param string * @param keep_upper If true, then any non-first character that is uppercase stays uppercase * @return */ public static String title(String string, boolean keep_upper) { StringBuilder sb = new StringBuilder(); String add = ""; boolean first = true; for (String part : TITLE_SPLIT.split(string)) { sb.append(add).append(part.substring(0, 1).toUpperCase()); int len = part.length(); if (len > 1) { if (keep_upper) { for (int i = 1; i < len; i++) { String c = part.substring(i, i + 1); String up = c.toUpperCase(); sb.append(c.equals(up) ? c : c.toLowerCase()); } // FOR } else { sb.append(part.substring(1).toLowerCase()); } } if (first) { add = " "; first = false; } } // FOR return (sb.toString()); } /** * Append SPACER to the front of each line in a string * * @param str * @return */ public static String addSpacers(String str) { StringBuilder sb = new StringBuilder(); for (String line : LINE_SPLIT.split(str)) { sb.append(SPACER).append(line).append("\n"); } // FOR return (sb.toString()); } /** * Python join() for arrays * * @param <T> * @param delimiter * @param items * @return */ @SuppressWarnings("unchecked") public static <T> String join(String delimiter, T... items) { return (join(null, delimiter, Arrays.asList(items))); } /** * Python join() for iterators * * @param delimiter * @param items * @return */ public static <T> String join(String delimiter, final Iterator<T> items) { return (join(null, delimiter, CollectionUtil.iterable(items))); } /** * Python join() for iterables * * @param delimiter * @param items * @return */ public static String join(String delimiter, Iterable<?> items) { return (join(null, delimiter, items)); } /** * Python join() with optional prefix * * @param prefix * @param delimiter * @param items * @return */ public static String join(String prefix, String delimiter, Iterable<?> items) { if (items == null) return (""); boolean hasPrefix = (prefix != null); StringBuilder sb = new StringBuilder(); int i = 0; for (Object x : items) { if (hasPrefix) sb.append(String.format(prefix, i)); sb.append(x != null ? x.toString() : x).append(delimiter); i++; } if (i == 0) return (""); sb.delete(sb.length() - delimiter.length(), sb.length()); return sb.toString(); } private static final char[] CHARACTERS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; private static char nibbleToHexChar(int nibble) { assert 0 <= nibble && nibble < CHARACTERS.length; return CHARACTERS[nibble]; } /** * Dump a ByteString to a text representation * Copied from: http://people.csail.mit.edu/evanj/hg/index.cgi/javatxn/file/tip/src/edu/mit/ExampleServer.java * @param bytes * @return */ public static StringBuilder hexDump(ByteString bytes) { StringBuilder out = new StringBuilder(); for (int i = 0; i < bytes.size(); ++i) { if (i != 0 && i % 2 == 0) { out.append(' '); } byte b = bytes.byteAt(i); out.append(nibbleToHexChar((b >> 4) & 0xf)); out.append(nibbleToHexChar(b & 0xf)); } return out; } /** * Pretty-print an object array * @param params * @param includeOffsets * @param includeClass * @return */ public static String toString(Object params[], boolean includeOffsets, boolean includeClass) { if (params == null) return ("null"); StringBuffer sb = new StringBuffer(); for (int i = 0; i < params.length; ++i) { if (i > 0) sb.append(", "); if (includeOffsets) sb.append("[" + i + "]="); // NULL if (params[i] == null) { sb.append("null"); } // ARRAY else if (ClassUtil.isArray(params[i])) { if (params[i] instanceof boolean[]) { sb.append(Arrays.toString((boolean[])params[i])); } else if (params[i] instanceof byte[]) { sb.append(Arrays.toString((byte[])params[i])); } else if (params[i] instanceof short[]) { sb.append(Arrays.toString((short[])params[i])); } else if (params[i] instanceof int[]) { sb.append(Arrays.toString((int[])params[i])); } else if (params[i] instanceof long[]) { sb.append(Arrays.toString((long[])params[i])); } else if (params[i] instanceof float[]) { sb.append(Arrays.toString((float[])params[i])); } else if (params[i] instanceof double[]) { sb.append(Arrays.toString((double[])params[i])); } else if (params[i] instanceof String[]) { sb.append(Arrays.toString((String[])params[i])); } else if (params[i] instanceof TimestampType[]) { sb.append(Arrays.toString((TimestampType[])params[i])); } else if (params[i] instanceof Object[]) { sb.append(Arrays.toString((Object[])params[i])); } else { sb.append(params[i].toString()); } if (includeClass) sb.append("(" + params[i].getClass().getSimpleName() + ")"); } else { sb.append(params[i].toString()); if (includeClass) sb.append("(" + params[i].getClass().getSimpleName() + ")"); } } return (sb.toString()); } }