| // Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| package com.android.tools.r8.utils; |
| |
| import com.android.tools.r8.errors.Unreachable; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.security.MessageDigest; |
| import java.security.NoSuchAlgorithmException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.function.Function; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| public class StringUtils { |
| public static char[] EMPTY_CHAR_ARRAY = {}; |
| public static final String[] EMPTY_ARRAY = {}; |
| public static final String LINE_SEPARATOR = System.getProperty("line.separator"); |
| public static final char BOM = '\uFEFF'; |
| |
| public enum BraceType { |
| PARENS, |
| SQUARE, |
| TUBORG, |
| NONE; |
| |
| public String left() { |
| switch (this) { |
| case PARENS: return "("; |
| case SQUARE: return "["; |
| case TUBORG: return "{"; |
| case NONE: return ""; |
| default: throw new Unreachable("Invalid brace type: " + this); |
| } |
| } |
| |
| public String right() { |
| switch (this) { |
| case PARENS: return ")"; |
| case SQUARE: return "]"; |
| case TUBORG: return "}"; |
| case NONE: return ""; |
| default: throw new Unreachable("Invalid brace type: " + this); |
| } |
| } |
| } |
| |
| public static String toASCIIString(String s) { |
| StringBuilder builder = new StringBuilder(); |
| for (char ch : s.toCharArray()) { |
| if (0x1f < ch && ch < 0x7f) { // 0 - 0x1f and 0x7f are control characters. |
| builder.append(ch); |
| } else { |
| builder.append("\\u").append(StringUtils.hexString(ch, 4, false)); |
| } |
| } |
| return builder.toString(); |
| } |
| |
| public static boolean appendNonEmpty( |
| StringBuilder builder, String pre, Object item, String post) { |
| if (item == null) { |
| return false; |
| } |
| String text = item.toString(); |
| if (!text.isEmpty()) { |
| if (pre != null) { |
| builder.append(pre); |
| } |
| builder.append(text); |
| if (post != null) { |
| builder.append(post); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| public static StringBuilder appendIndent(StringBuilder builder, String subject, int indent) { |
| for (int i = 0; i < indent; i++) { |
| builder.append(" "); |
| } |
| builder.append(subject); |
| return builder; |
| } |
| |
| public static StringBuilder appendLeftPadded(StringBuilder builder, String subject, int width) { |
| for (int i = subject.length(); i < width; i++) { |
| builder.append(" "); |
| } |
| builder.append(subject); |
| return builder; |
| } |
| |
| public static StringBuilder appendRightPadded(StringBuilder builder, String subject, int width) { |
| builder.append(subject); |
| for (int i = subject.length(); i < width; i++) { |
| builder.append(" "); |
| } |
| return builder; |
| } |
| |
| public static <T> StringBuilder append(StringBuilder builder, Collection<T> collection) { |
| return append(builder, collection, ", ", BraceType.PARENS); |
| } |
| |
| public static <T> StringBuilder append(StringBuilder builder, Collection<T> collection, |
| String seperator, BraceType brace) { |
| builder.append(brace.left()); |
| boolean first = true; |
| for (T element : collection) { |
| if (first) { |
| first = false; |
| } else { |
| builder.append(seperator); |
| } |
| builder.append(element); |
| } |
| builder.append(brace.right()); |
| return builder; |
| } |
| |
| public static <T> String join(Collection<T> collection, String separator) { |
| return join(collection, separator, BraceType.NONE); |
| } |
| |
| public static String join(String separator, String... strings) { |
| return join(Arrays.asList(strings), separator, BraceType.NONE); |
| } |
| |
| public static <T> String join(Collection<T> collection, String separator, BraceType brace) { |
| return join(collection, separator, brace, Object::toString); |
| } |
| |
| public static <T> String join(Collection<T> collection, String separator, BraceType brace, |
| Function<T, String> fn) { |
| StringBuilder builder = new StringBuilder(); |
| append(builder, ListUtils.map(collection, fn), separator, brace); |
| return builder.toString(); |
| } |
| |
| public static String lines(List<String> lines) { |
| StringBuilder builder = new StringBuilder(); |
| for (String line : lines) { |
| builder.append(line).append(LINE_SEPARATOR); |
| } |
| return builder.toString(); |
| } |
| |
| public static String lines(String... lines) { |
| return lines(Arrays.asList(lines)); |
| } |
| |
| public static String withNativeLineSeparator(String s) { |
| s = s.replace("\r\n", "\n"); |
| if (LINE_SEPARATOR.equals("\r\n")) { |
| return s.replace("\n", "\r\n"); |
| } else { |
| assert LINE_SEPARATOR.equals("\n"); |
| return s; |
| } |
| } |
| |
| public static String joinLines(String... lines) { |
| return join(LINE_SEPARATOR, lines); |
| } |
| |
| public static <T> String joinLines(Collection<T> collection) { |
| return join(collection, LINE_SEPARATOR, BraceType.NONE); |
| } |
| |
| public static List<String> splitLines(String content) { |
| int length = content.length(); |
| List<String> lines = new ArrayList<>(); |
| int start = 0; |
| for (int i = 0; i < length; i++) { |
| char c = content.charAt(i); |
| int end = i; |
| if (c == '\r' && i + 1 < length && content.charAt(i + 1) == '\n') { |
| ++i; |
| } else if (c != '\n') { |
| continue; |
| } |
| lines.add(content.substring(start, end)); |
| start = i + 1; |
| } |
| if (start < length) { |
| String line = content.substring(start); |
| if (!line.isEmpty()) { |
| lines.add(line); |
| } |
| } |
| return lines; |
| } |
| |
| public static String zeroPrefix(int i, int width) { |
| return zeroPrefixString(Integer.toString(i), width); |
| } |
| |
| private static String zeroPrefixString(String s, int width) { |
| String prefix = "0000000000000000"; |
| assert(width <= prefix.length()); |
| int prefixLength = width - s.length(); |
| if (prefixLength > 0) { |
| StringBuilder builder = new StringBuilder(); |
| builder.append(prefix, 0, prefixLength); |
| builder.append(s); |
| return builder.toString(); |
| } else { |
| return s; |
| } |
| } |
| |
| public static String hexString(int value, int width) { |
| return hexString(value, width, true); |
| } |
| |
| public static String hexString(int value, int width, boolean zeroXPrefix) { |
| assert(0 <= width && width <= 8); |
| String prefix = zeroXPrefix ? "0x" : ""; |
| String hex = Integer.toHexString(value); |
| if (value >= 0) { |
| return prefix + zeroPrefixString(hex, width); |
| } else { |
| // Negative ints are always formatted as 8 characters. |
| assert(hex.length() == 8); |
| return prefix + hex; |
| } |
| } |
| |
| public static String hexString(long value, int width) { |
| return hexString(value, width, true); |
| } |
| |
| public static String hexString(long value, int width, boolean zeroXPrefix) { |
| assert(0 <= width && width <= 16); |
| String prefix = zeroXPrefix ? "0x" : ""; |
| String hex = Long.toHexString(value); |
| if (value >= 0) { |
| return prefix + zeroPrefixString(hex, width); |
| } else { |
| // Negative longs are always formatted as 16 characters. |
| assert(hex.length() == 16); |
| return prefix + hex; |
| } |
| } |
| |
| public static String computeMD5Hash(String name) { |
| byte[] digest = null; |
| try { |
| MessageDigest m = MessageDigest.getInstance("MD5"); |
| m.reset(); |
| m.update(name.getBytes()); |
| digest = m.digest(); |
| } catch (NoSuchAlgorithmException e) { |
| throw new RuntimeException(e); |
| } |
| return Arrays.toString(digest); |
| } |
| |
| |
| public static String times(String string, int count) { |
| StringBuilder builder = new StringBuilder(); |
| while (--count >= 0) { |
| builder.append(string); |
| } |
| return builder.toString(); |
| } |
| |
| public static boolean isBOM(int codePoint) { |
| return codePoint == BOM; |
| } |
| |
| public static boolean isWhitespace(int codePoint) { |
| return Character.isWhitespace(codePoint) || isBOM(codePoint); |
| } |
| |
| public static String stripLeadingBOM(String s) { |
| if (s.length() > 0 && s.charAt(0) == StringUtils.BOM) { |
| return s.substring(1); |
| } else { |
| return s; |
| } |
| } |
| |
| public static String trim(String s) { |
| int beginIndex = 0; |
| int endIndex = s.length(); |
| while (beginIndex < endIndex && isWhitespace(s.charAt(beginIndex))) { |
| beginIndex++; |
| } |
| while (endIndex - 1 > beginIndex && isWhitespace(s.charAt(endIndex - 1))) { |
| endIndex--; |
| } |
| if (beginIndex > 0 || endIndex < s.length()) { |
| return s.substring(beginIndex, endIndex); |
| } else { |
| return s; |
| } |
| } |
| |
| /** Returns true if {@param s} only contains the characters [0-9]. */ |
| public static boolean onlyContainsDigits(String s) { |
| for (int i = 0; i < s.length(); i++) { |
| char c = s.charAt(i); |
| if (!Character.isDigit(c)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| public static char lastChar(String s) { |
| return charFromEnd(s, 0); |
| } |
| |
| public static char charFromEnd(String s, int charsFromEnd) { |
| assert s.length() > charsFromEnd; |
| return s.charAt(s.length() - (charsFromEnd + 1)); |
| } |
| |
| public static int firstNonWhitespaceCharacter(String string) { |
| for (int i = 0; i < string.length(); i++) { |
| if (!isWhitespace(string.charAt(i))) { |
| return i; |
| } |
| } |
| return string.length(); |
| } |
| |
| public static String replaceAll(String subject, String target, String replacement) { |
| return subject.replaceAll(Pattern.quote(target), Matcher.quoteReplacement(replacement)); |
| } |
| |
| public static String stacktraceAsString(Throwable throwable) { |
| StringWriter sw = new StringWriter(); |
| throwable.printStackTrace(new PrintWriter(sw)); |
| return sw.toString(); |
| } |
| } |