Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 1 | // Copyright 2014 The Bazel Authors. All rights reserved. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | package com.google.devtools.build.lib.util; |
| 15 | |
| 16 | import com.google.common.base.Joiner; |
| 17 | import com.google.common.collect.ImmutableList; |
| 18 | import com.google.common.escape.CharEscaperBuilder; |
| 19 | import com.google.common.escape.Escaper; |
| 20 | |
| 21 | import java.util.ArrayList; |
| 22 | import java.util.Collection; |
| 23 | import java.util.List; |
| 24 | import java.util.Map; |
| 25 | |
| 26 | /** |
| 27 | * Various utility methods operating on strings. |
| 28 | */ |
| 29 | public class StringUtilities { |
| 30 | |
| 31 | private static final Joiner NEWLINE_JOINER = Joiner.on('\n'); |
| 32 | |
| 33 | private static final Escaper KEY_ESCAPER = new CharEscaperBuilder() |
| 34 | .addEscape('!', "!!") |
| 35 | .addEscape('<', "!<") |
| 36 | .addEscape('>', "!>") |
| 37 | .toEscaper(); |
| 38 | |
| 39 | private static final Escaper CONTROL_CHAR_ESCAPER = new CharEscaperBuilder() |
| 40 | .addEscape('\r', "\\r") |
| 41 | .addEscapes(new char[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, /*13=\r*/ |
| 42 | 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 127}, "<?>") |
| 43 | .toEscaper(); |
| 44 | |
| 45 | /** |
| 46 | * Java doesn't have multiline string literals, so having to join a bunch |
| 47 | * of lines is a very common problem. So, here's a static method that we |
| 48 | * can static import in such situations. |
| 49 | */ |
| 50 | public static String joinLines(String... lines) { |
| 51 | return NEWLINE_JOINER.join(lines); |
| 52 | } |
| 53 | |
| 54 | /** |
| 55 | * A corollary to {@link #joinLines(String[])} for collections. |
| 56 | */ |
| 57 | public static String joinLines(Collection<String> lines) { |
| 58 | return NEWLINE_JOINER.join(lines); |
| 59 | } |
| 60 | |
| 61 | /** |
| 62 | * combineKeys(x1, ..., xn): |
| 63 | * Computes a string that encodes the sequence |
| 64 | * x1, ..., xn. Distinct sequences map to distinct strings. |
| 65 | * |
| 66 | * The encoding is intended to be vaguely human-readable. |
| 67 | */ |
| 68 | public static String combineKeys(Iterable<String> parts) { |
| 69 | final StringBuilder buf = new StringBuilder(128); |
| 70 | for (String part : parts) { |
| 71 | // We enclose each part in angle brackets to separate them. Some |
| 72 | // trickiness is required to ensure that the result is unique (distinct |
| 73 | // sequences map to distinct strings): we escape any angle bracket |
| 74 | // characters in the parts by preceding them with an escape character |
| 75 | // (we use "!") and we also need to escape any escape characters. |
| 76 | buf.append('<'); |
| 77 | buf.append(KEY_ESCAPER.escape(part)); |
| 78 | buf.append('>'); |
| 79 | } |
| 80 | return buf.toString(); |
| 81 | } |
| 82 | |
| 83 | /** |
| 84 | * combineKeys(x1, ..., xn): |
| 85 | * Computes a string that encodes the sequence |
| 86 | * x1, ..., xn. Distinct sequences map to distinct strings. |
| 87 | * |
| 88 | * The encoding is intended to be vaguely human-readable. |
| 89 | */ |
| 90 | public static String combineKeys(String... parts) { |
| 91 | return combineKeys(ImmutableList.copyOf(parts)); |
| 92 | } |
| 93 | |
| 94 | /** |
| 95 | * Replaces all occurrences of 'literal' in 'input' with 'replacement'. |
| 96 | * Like {@link String#replaceAll(String, String)} but for literal Strings |
| 97 | * instead of regular expression patterns. |
| 98 | * |
| 99 | * @param input the input String |
| 100 | * @param literal the literal String to replace in 'input'. |
| 101 | * @param replacement the replacement String to replace 'literal' in 'input'. |
| 102 | * @return the 'input' String with all occurrences of 'literal' replaced with |
| 103 | * 'replacement'. |
| 104 | */ |
| 105 | public static String replaceAllLiteral(String input, String literal, |
| 106 | String replacement) { |
| 107 | int literalLength = literal.length(); |
| 108 | if (literalLength == 0) { |
| 109 | return input; |
| 110 | } |
| 111 | StringBuilder result = new StringBuilder( |
| 112 | input.length() + replacement.length()); |
| 113 | int start = 0; |
| 114 | int index = 0; |
| 115 | |
| 116 | while ((index = input.indexOf(literal, start)) >= 0) { |
Ulf Adams | 07dba94 | 2015-03-05 14:47:37 +0000 | [diff] [blame] | 117 | result.append(input, start, index); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 118 | result.append(replacement); |
| 119 | start = index + literalLength; |
| 120 | } |
| 121 | result.append(input.substring(start)); |
| 122 | return result.toString(); |
| 123 | } |
| 124 | |
| 125 | /** |
| 126 | * Creates a simple key-value table of the form |
| 127 | * |
| 128 | * <pre> |
| 129 | * key: some value |
| 130 | * another key: some other value |
| 131 | * yet another key: and so on ... |
| 132 | * </pre> |
| 133 | * |
| 134 | * The return value will not include a final {@code "\n"}. |
| 135 | */ |
| 136 | public static String layoutTable(Map<String, String> data) { |
| 137 | List<String> tableLines = new ArrayList<>(); |
| 138 | for (Map.Entry<String, String> entry : data.entrySet()) { |
| 139 | tableLines.add(entry.getKey() + ": " + entry.getValue()); |
| 140 | } |
| 141 | return NEWLINE_JOINER.join(tableLines); |
| 142 | } |
| 143 | |
| 144 | /** |
| 145 | * Returns an easy-to-read string approximation of a number of bytes, |
| 146 | * e.g. "21MB". Note, these are IEEE units, i.e. decimal not binary powers. |
| 147 | */ |
| 148 | public static String prettyPrintBytes(long bytes) { |
| 149 | if (bytes < 1E4) { // up to 10KB |
| 150 | return bytes + "B"; |
| 151 | } else if (bytes < 1E7) { // up to 10MB |
| 152 | return ((int) (bytes / 1E3)) + "KB"; |
| 153 | } else if (bytes < 1E11) { // up to 100GB |
| 154 | return ((int) (bytes / 1E6)) + "MB"; |
| 155 | } else { |
| 156 | return ((int) (bytes / 1E9)) + "GB"; |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | /** |
| 161 | * Returns true if 'source' contains 'target' as a sub-array. |
| 162 | */ |
| 163 | public static boolean containsSubarray(char[] source, char[] target) { |
| 164 | if (target.length > source.length) { |
| 165 | return false; |
| 166 | } |
| 167 | for (int i = 0; i < source.length - target.length + 1; i++) { |
| 168 | boolean matches = true; |
| 169 | for (int j = 0; j < target.length; j++) { |
| 170 | if (source[i + j] != target[j]) { |
| 171 | matches = false; |
| 172 | break; |
| 173 | } |
| 174 | } |
| 175 | if (matches) { |
| 176 | return true; |
| 177 | } |
| 178 | } |
| 179 | return false; |
| 180 | } |
| 181 | |
| 182 | /** |
| 183 | * Replace control characters with visible strings. |
| 184 | * @return the sanitized string. |
| 185 | */ |
| 186 | public static String sanitizeControlChars(String message) { |
| 187 | return CONTROL_CHAR_ESCAPER.escape(message); |
| 188 | } |
| 189 | |
| 190 | /** |
| 191 | * Converts a Java style function name to a Python style function name the following way: |
| 192 | * every upper case character gets replaced with an underscore and its lower case counterpart. |
| 193 | * <p>E.g. fooBar --> foo_bar |
| 194 | */ |
| 195 | public static String toPythonStyleFunctionName(String javaStyleFunctionName) { |
| 196 | StringBuilder sb = new StringBuilder(); |
| 197 | for (int i = 0; i < javaStyleFunctionName.length(); i++) { |
| 198 | char c = javaStyleFunctionName.charAt(i); |
| 199 | if (Character.isUpperCase(c)) { |
| 200 | sb.append('_').append(Character.toLowerCase(c)); |
| 201 | } else { |
| 202 | sb.append(c); |
| 203 | } |
| 204 | } |
| 205 | return sb.toString(); |
| 206 | } |
| 207 | } |