com.foundationdb.util.Strings.java Source code

Java tutorial

Introduction

Here is the source code for com.foundationdb.util.Strings.java

Source

/**
 * Copyright (C) 2009-2013 FoundationDB, LLC
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.foundationdb.util;

import com.foundationdb.server.error.InvalidParameterValueException;
import com.foundationdb.sql.unparser.NodeToString;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;

import com.google.common.io.BaseEncoding;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.JarURLConnection;
import java.net.URL;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.*;
import java.util.Map.Entry;
import java.util.jar.JarEntry;

/**
 * String utils.
 */
public abstract class Strings {

    public static final String NL = nl();

    public static List<String> entriesToString(Map<?, ?> map) {
        List<String> result = new ArrayList<>(map.size());
        for (Map.Entry<?, ?> entry : map.entrySet())
            result.add(entry.toString());
        return result;
    }

    private static class ListAppendable implements Appendable {
        private final List<String> list;

        public ListAppendable(List<String> list) {
            this.list = list;
        }

        @Override
        public Appendable append(CharSequence csq) throws IOException {
            list.add(csq.toString());
            return this;
        }

        @Override
        public Appendable append(CharSequence csq, int start, int end) throws IOException {
            return append(csq.subSequence(start, end).toString());
        }

        @Override
        public Appendable append(char c) throws IOException {
            return append(String.valueOf(c));
        }
    }

    /**
     * Gets the system <tt>line.separator</tt> newline.
     * @return <tt>System.getProperty("line.separator")</tt>
     */
    public static String nl() {
        String nl = System.getProperty("line.separator");
        if (nl == null) {
            throw new NullPointerException("couldn't find system property line.separator");
        }
        return nl;
    }

    /**
     * Joins the given Strings into a single, newline-delimited String. Newline is the system-dependent one as
     * defined by the system property <tt>line.separator</tt>.
     * @param strings the strings
     * @return the String
     */
    public static String join(Collection<?> strings) {
        return join(strings, nl());
    }

    /**
     * Joins the given Strings into a single, newline-delimited String. Newline is the system-dependent one as
     * defined by the system property <tt>line.separator</tt>.
     * @param strings the strings
     * @return the String
     */
    public static String join(Object... strings) {
        return join(Arrays.asList(strings));
    }

    /**
     * Joins the given Strings into a single String with the given delimiter. The last String in the list will
     * not have the delimiter appended. If the list is empty, this returns an empty string.
     * @param strings a list of strings. May not be null.
     * @param delimiter the delimiter between strings; this will be inserted <tt>(strings.size() - 1)</tt> times.
     * May not be null.  
     * @return the joined string
     */
    public static String join(Collection<?> strings, String delimiter) {
        if (strings.size() == 0) {
            return "";
        }
        StringBuilder builder = new StringBuilder(30 * strings.size()); // rough guess for capacity!
        for (Object string : strings) {
            builder.append(string).append(delimiter);
        }
        builder.setLength(builder.length() - delimiter.length());
        return builder.toString();
    }

    public static String repeatString(String str, int count) {
        StringBuilder sb = new StringBuilder(str.length() * count);
        while (count-- > 0) {
            sb.append(str);
        }
        return sb.toString();
    }

    public static List<String> stringAndSort(Collection<?> inputs) {
        List<String> results = new ArrayList<>(inputs.size());

        for (Object item : inputs) {
            String asString = stringAndSort(item);
            results.add(asString);
        }

        Collections.sort(results);
        return results;
    }

    public static List<String> stringAndSort(Map<?, ?> inputs) {
        // step 1: get the key-value pairs into a multimap. We need a multimap because multiple keys may have the
        // same toString. For instance, { 1 : "int", 1L : "long" } would become a multimap { "1" : ["int", "long"] }
        Multimap<String, String> multiMap = ArrayListMultimap.create();
        for (Map.Entry<?, ?> inputEntry : inputs.entrySet()) {
            String keyString = stringAndSort(inputEntry.getKey());
            String valueString = stringAndSort(inputEntry.getValue());
            multiMap.put(keyString, valueString);
        }
        // step 2: Flatten the multimap into a Map<String,String>, sorting by keys as you go.
        Map<String, String> sortedAndFlattened = new TreeMap<>();
        for (Entry<String, Collection<String>> multiMapEntry : multiMap.asMap().entrySet()) {
            String keyString = multiMapEntry.getKey();
            String valueString = stringAndSort(multiMapEntry.getValue()).toString();
            String duplicate = sortedAndFlattened.put(keyString, valueString);
            assert duplicate == null : duplicate;
        }

        // step 3: Flatten the map into a List<String>
        List<String> results = new ArrayList<>(inputs.size());
        for (Entry<String, String> entry : sortedAndFlattened.entrySet()) {
            results.add(entry.toString());
        }
        return results;
    }

    private static String stringAndSort(Object item) {
        if (item instanceof Collection) {
            return stringAndSort((Collection<?>) item).toString();
        } else if (item instanceof Map) {
            return stringAndSort((Map<?, ?>) item).toString();
        } else {
            return String.valueOf(item);
        }
    }

    /**
     * Dumps the content of a resource into a List<String>, where each element is one line of the resource.
     * @param forClass the class whose resource we should get; if null, will get the default
     * <tt>ClassLoader.getSystemResourceAsStream</tt>.
     * @param path the name of the resource
     * @return a list of lines in the resource
     * @throws IOException if the given resource doesn't exist or can't be properly read
     */
    public static List<String> dumpResource(Class<?> forClass, String path) throws IOException {
        InputStream is = (forClass == null) ? ClassLoader.getSystemResourceAsStream(path)
                : forClass.getResourceAsStream(path);
        if (is == null) {
            throw new FileNotFoundException("For class " + forClass + ": " + path);
        }
        return readStream(is);
    }

    public static List<String> dumpURLs(Enumeration<URL> urls) throws IOException {
        List<String> result = new ArrayList<>();
        while (urls.hasMoreElements()) {
            URL next = urls.nextElement();
            LOG.debug("reading URL: {}", next);
            boolean readAsStream = true;
            if ("jar".equals(next.getProtocol())) {
                JarURLConnection connection = (JarURLConnection) next.openConnection();
                if (connection.getJarEntry().isDirectory()) {
                    readJarConnectionTo(connection, result);
                    readAsStream = false;
                }
            }
            if (readAsStream) {
                InputStream is = next.openStream();
                readStreamTo(is, new ListAppendable(result), false);
            }
        }
        return result;
    }

    @SuppressWarnings("unused") // primarily useful in debuggers
    public static String[] dumpException(Throwable t) {
        StringWriter stringWriter = new StringWriter();
        PrintWriter printWriter = new PrintWriter(stringWriter);
        t.printStackTrace(printWriter);
        printWriter.flush();
        stringWriter.flush();
        return stringWriter.toString().split("\\n");
    }

    public static String toOctal(byte[] bytes) {
        StringBuilder out = new StringBuilder();
        for (byte b : bytes) {
            out.append(String.format("\\%03o", b));
        }
        return out.toString();
    }

    public static String hex(byte[] bytes) {
        return hex(bytes, 0, bytes.length);
    }

    public static String hex(ByteSource byteSource) {
        return hex(byteSource.byteArray(), byteSource.byteArrayOffset(), byteSource.byteArrayLength());
    }

    public static String hex(byte[] bytes, int start, int length) {
        return BaseEncoding.base16().encode(bytes, start, length);
    }

    /** For example, '0' returns 0, 'a' or 'A' returns 10, etc */
    private static int hexCharToInt(char c) {
        int lower = c | 32;
        if (lower >= '0' && lower <= '9')
            return lower - '0';
        if (lower >= 'a' && lower <= 'f')
            return 10 + lower - 'a';
        throw new InvalidParameterValueException("Invalid HEX digit: " + c);
    }

    /** For example, ('2','0') returns 32 */
    private static byte hexCharsToByte(char highChar, char lowChar) {
        return (byte) ((hexCharToInt(highChar) << 4) + hexCharToInt(lowChar));
    }

    public static ByteSource parseHexWithout0x(String st) {
        int odd = st.length() & 0x01;
        int outputLen = (st.length() >> 1) + odd;
        byte ret[] = new byte[outputLen];
        int si = 0, ri = 0;
        if (odd == 1) {
            ret[ri++] = (byte) hexCharToInt(st.charAt(si++));
        }
        while (ri < ret.length) {
            ret[ri++] = hexCharsToByte(st.charAt(si++), st.charAt(si++));
        }
        return new WrappingByteSource(ret);
    }

    public static ByteSource parseHex(String string) {
        if (!string.startsWith("0x")) {
            throw new RuntimeException("not a hex string");
        }

        byte[] ret = new byte[(string.length() - 2) / 2];

        int resultIndex = 0;
        for (int strIndex = 2; strIndex < string.length(); ++strIndex) {
            final char strChar = string.charAt(strIndex);
            if (!Character.isWhitespace(strChar)) {
                int high = (Character.digit(strChar, 16)) << 4;
                char lowChar = string.charAt(++strIndex);
                int low = (Character.digit(lowChar, 16));
                ret[resultIndex++] = (byte) (low + high);
            }
        }

        return new WrappingByteSource().wrap(ret, 0, resultIndex);
    }

    public static List<String> readStream(InputStream is) throws IOException {
        List<String> result = new ArrayList<>();
        readStreamTo(is, new ListAppendable(result), false);
        return result;
    }

    public static void readStreamTo(InputStream is, Appendable out, boolean keepNL) throws IOException {
        readerTo(bufferedReader(is), out, keepNL);
    }

    private static void readerTo(BufferedReader reader, Appendable out, boolean keepNL) throws IOException {
        try {
            if (keepNL) {
                CharBuffer buffer = CharBuffer.allocate(1024);
                while (reader.read(buffer) != -1) {
                    buffer.flip();
                    out.append(buffer);
                    buffer.clear();
                }
            } else {
                String line;
                while ((line = reader.readLine()) != null) {
                    out.append(line);
                }
            }
        } finally {
            reader.close();
        }
    }

    private static void readJarConnectionTo(JarURLConnection connection, List<String> result) throws IOException {
        assert connection.getJarEntry().isDirectory() : "not a dir: " + connection.getJarEntry();
        // put into entries only the children of the connection's entry, and trim off the entry prefix
        String base = connection.getEntryName();
        Enumeration<JarEntry> enumeration = connection.getJarFile().entries();
        while (enumeration.hasMoreElements()) {
            JarEntry entry = enumeration.nextElement();
            if (entry.getName().startsWith(base))
                result.add(entry.getName().substring(base.length()));
        }
    }

    public static <T> String toString(Multimap<T, ?> map) {
        StringBuilder sb = new StringBuilder();
        for (Iterator<T> keysIter = map.keySet().iterator(); keysIter.hasNext();) {
            T key = keysIter.next();
            sb.append(key).append(" => ");
            for (Iterator<?> valsIter = map.get(key).iterator(); valsIter.hasNext();) {
                sb.append(valsIter.next());
                if (valsIter.hasNext())
                    sb.append(", ");
            }
            if (keysIter.hasNext())
                sb.append(nl());
        }
        return sb.toString();
    }

    public static String stripr(String input, String suffix) {
        if (input == null || suffix == null)
            return input;
        return input.endsWith(suffix) ? input.substring(0, input.length() - suffix.length()) : input;
    }

    private static final Logger LOG = LoggerFactory.getLogger(Strings.class);

    public static List<String> dumpFile(File file) throws IOException {
        List<String> results = new ArrayList<>();
        readerTo(bufferedReader(new FileInputStream(file)), new ListAppendable(results), false);
        return results;
    }

    public static String dumpFileToString(File file) throws IOException {
        StringBuilder builder = new StringBuilder();
        readerTo(bufferedReader(new FileInputStream(file)), builder, true);
        return builder.toString();
    }

    public static List<String> mapToString(Collection<?> collection) {
        // are lambdas here yet?!
        List<String> strings = new ArrayList<>(collection.size());
        for (Object o : collection)
            strings.add(String.valueOf(o));
        return strings;
    }

    public static boolean equalCharsets(Charset one, String two) {
        return one.name().equals(two) || one.equals(Charset.forName(two));
    }

    private static BufferedReader bufferedReader(InputStream is) {
        try {
            return new BufferedReader(new InputStreamReader(is, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    public static String formatMD5(byte[] md5, boolean toLowerCase) {
        BaseEncoding encoder = toLowerCase ? BaseEncoding.base16().lowerCase() : BaseEncoding.base16().upperCase();
        return encoder.encode(md5);
    }

    public static String truncateIfNecessary(String str, int codePointCount) {
        // Try to avoid scanning the string for surrogates, which are rare in the wild.
        int nchars = str.length();
        if (nchars <= codePointCount)
            return str;
        int ncode = str.codePointCount(0, nchars);
        if (ncode <= codePointCount)
            return str;
        if (nchars == ncode)
            return str.substring(0, codePointCount);
        else
            return str.substring(0, str.offsetByCodePoints(0, codePointCount));
    }

    public static String toBase64(byte[] bytes) {
        return toBase64(bytes, 0, bytes.length);
    }

    public static String toBase64(byte[] bytes, int offset, int length) {
        return BaseEncoding.base64().encode(bytes, offset, length);
    }

    public static byte[] fromBase64(CharSequence cs) {
        return BaseEncoding.base64().decode(cs);
    }

    /**
     * Split a period delimited string of identifiers into an array of
     * constituent pieces. Up to {@code maxParts} many identifiers will
     * be returned and non-quoted identifiers will be lower-cased.
     * <p>
     * For example,
     * <pre>
     * (test.t, 1)  => [test]
     * (test.t, 2)  => [test, t]
     * (test.t, 3)  => ["", test, t]
     * (A.B,    2)  => [a, b]
     * ("a.B".t, 2) => [a.B, t]
     * </pre>
     * </p>
     */
    public static String[] parseQualifiedName(String arg, int maxParts) {
        assert maxParts > 0 : maxParts;
        String[] result = new String[maxParts];
        int resIndex = 0;
        char lastQuote = 0;
        int prevEnd = 0;
        for (int i = 0; i < arg.length(); ++i) {
            char c = arg.charAt(i);
            boolean take = false;
            boolean toLower = true;
            if (lastQuote != 0) {
                if (c == lastQuote) {
                    take = true;
                    toLower = false;
                    lastQuote = 0;
                }
            } else if (c == '"' || c == '`') {
                lastQuote = c;
                prevEnd = i + 1;
            } else if (c == '.') {
                take = true;
            }
            if (take) {
                if (prevEnd < i) {
                    result[resIndex++] = consumeIdentifier(arg, prevEnd, i, toLower);
                }
                prevEnd = i + 1;
            }
        }
        if ((resIndex < maxParts) && (prevEnd < arg.length())) {
            result[resIndex++] = consumeIdentifier(arg, prevEnd, arg.length(), lastQuote == 0);
        }
        int diff = maxParts - resIndex;
        if (diff > 0) {
            // Shift found and empty fill missing
            System.arraycopy(result, 0, result, maxParts - resIndex, resIndex);
            for (int i = 0; i < diff; ++i) {
                result[i] = "";
            }
        }
        return result;
    }

    public static String quotedIdent(String s, char quote, boolean force) {

        String quoteS = Character.toString(quote);

        if (s.contains(quoteS)) {
            s = s.replaceAll(("[" + quoteS + "]"), quoteS + quoteS);
        }

        if (!force && !NodeToString.isReserved(s) && s.matches("[a-z][_a-z0-9$]*")) {
            return s;
        } else {
            return quote + s + quote;
        }
    }

    public static String escapeIdentifier(String s) {
        return String.format("\"%s\"", s.replace("\"", "\"\""));
    }

    //
    // Internal
    //

    private static String consumeIdentifier(String arg, int begin, int end, boolean toLower) {
        String s = arg.substring(begin, end);
        if (toLower) {
            s = s.toLowerCase();
        }
        return s;
    }
}