Java tutorial
/* * StringUtil.java * * Copyright (C) 2009-12 by RStudio, Inc. * * Unless you have received this program directly from RStudio pursuant * to the terms of a commercial license agreement with RStudio, then * this program is licensed to you under the terms of version 3 of the * GNU Affero General Public License. This program is distributed WITHOUT * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details. * */ package org.rstudio.core.client; import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.i18n.client.NumberFormat; import com.google.gwt.user.client.Window; import org.rstudio.core.client.dom.DomMetrics; import org.rstudio.core.client.files.FileSystemItem; import org.rstudio.core.client.regex.Match; import org.rstudio.core.client.regex.Pattern; import org.rstudio.core.client.regex.Pattern.ReplaceOperation; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; public class StringUtil { public static String padRight(String value, int minWidth) { if (value.length() >= minWidth) return value; StringBuilder out = new StringBuilder(); for (int i = minWidth - value.length(); i > 0; i--) out.append(' '); out.append(value); return out.toString(); } public static int parseInt(String value, int defaultValue) { try { return Integer.parseInt(value); } catch (NumberFormatException nfe) { return defaultValue; } } public static String formatDate(Date date) { if (date == null) return ""; return DATE_FORMAT.format(date); } public static String formatFileSize(long size) { return formatFileSize(new Long(size).intValue()); } // Given a raw size, convert it to a human-readable value // (e.g. 11580 -> "11.3 KB"). Note that this routine must generally avoid // implicit casts and use only ints; GWT's JavaScript compiler will truncate // values it believes to be ints to Int32 max/min (+/- 2 billion) during // type checking, and this function deals in file and object sizes larger // than that. public static String formatFileSize(int size) { int i = 0, divisor = 1; for (; nativeDivide(size, divisor) > 1024 && i < LABELS.length; i++) { divisor *= 1024; } return FORMAT.format((double) size / divisor) + " " + LABELS[i]; } // Peform an integer division and return the result. GWT's division operator // truncates the result to Int32 range. public static native int nativeDivide(int num, int denom) /*-{ return num / denom; }-*/; public static String prettyFormatNumber(double number) { return PRETTY_NUMBER_FORMAT.format(number); } public static String formatGeneralNumber(long number) { String val = number + ""; if (val.length() < 5 || (number < 0 && val.length() < 6)) return val; return NumberFormat.getFormat("0,000").format(number); } public static String formatPercent(double number) { return NumberFormat.getPercentFormat().format(number); } public static Size characterExtent(String text) { // split into lines and find the maximum line width String[] lines = text.split("\n"); int maxWidth = 0; for (int i = 0; i < lines.length; i++) { int width = lines[i].length(); if (width > maxWidth) maxWidth = width; } return new Size(maxWidth, lines.length); } public static String chomp(String string) { if (string.endsWith("\n")) return string.substring(0, string.length() - 1); return string; } public static boolean isNullOrEmpty(String val) { return val == null || val.length() == 0; } // WARNING: I'm pretty sure this will fail for UTF-8 public static String textToRLiteral(String value) { StringBuffer sb = new StringBuffer(); sb.append('"'); for (char c : value.toCharArray()) { switch (c) { case '"': sb.append("\\\""); break; case '\n': sb.append("\\n"); break; case '\r': sb.append("\\r"); break; case '\t': sb.append("\\t"); break; case '\b': sb.append("\\b"); break; case '\f': sb.append("\\f"); break; case '\\': sb.append("\\\\"); break; default: if (c < 32 || c > 126) sb.append("\\x").append(toHex(c)); else sb.append(c); break; } } sb.append('"'); return sb.toString(); } private static String toHex(char c) { String table = "0123456789ABCDEF"; return table.charAt((c >> 8) & 0xF) + "" + table.charAt(c & 0xF); } public static String toRSymbolName(String name) { if (!name.matches("^[a-zA-Z_.][a-zA-Z0-9_.]*$") || isRKeyword(name)) { return "`" + name + "`"; } else return name; } private static boolean isRKeyword(String identifier) { String ALL_KEYWORDS = "|NULL|NA|TRUE|FALSE|T|F|Inf|NaN|NA_integer_|NA_real_|NA_character_|NA_complex_|function|while|repeat|for|if|in|else|next|break|...|"; if (identifier.length() > 20 || identifier.contains("|")) return false; return ALL_KEYWORDS.indexOf("|" + identifier + "|") >= 0; } public static String notNull(String s) { return s == null ? "" : s; } public static String indent(String str, String indent) { if (isNullOrEmpty(str)) return str; return indent + str.replaceAll("\n", "\n" + indent); } public static String join(Collection<?> collection, String delim) { String currDelim = ""; StringBuilder output = new StringBuilder(); for (Object el : collection) { output.append(currDelim).append(el == null ? "" : el.toString()); currDelim = delim; } return output.toString(); } public static String firstNotNullOrEmpty(String[] strings) { for (String s : strings) if (!isNullOrEmpty(s)) return s; return null; } public static String shortPathName(FileSystemItem item, int maxWidth) { return shortPathName(item, "gwt-Label", maxWidth); } public static String shortPathName(FileSystemItem item, String styleName, int maxWidth) { // measure HTML and truncate if necessary String path = item.getPath(); Size textSize = DomMetrics.measureHTML(path, styleName); if (textSize.width >= maxWidth) { // shortened directory nam if (item.getParentPath() != null && item.getParentPath().getParentPath() != null) { path = ".../" + item.getParentPath().getName() + "/" + item.getName(); } } return path; } public static Iterable<String> getLineIterator(final String text) { return new Iterable<String>() { @Override public Iterator<String> iterator() { return new Iterator<String>() { private int pos = 0; private Pattern newline = Pattern.create("\\r?\\n"); @Override public boolean hasNext() { return pos < text.length(); } @Override public String next() { if (pos >= text.length()) return null; Match match = newline.match(text, pos); String result; if (match == null) { result = text.substring(pos); pos = text.length(); } else { result = text.substring(pos, match.getIndex()); pos = match.getIndex() + match.getValue().length(); } return result; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } }; } /** * Removes empty or whitespace-only lines from the beginning and end of the * string. */ public static String trimBlankLines(String data) { data = Pattern.create("^[\\r\\n\\t ]*\\n", "g").replaceAll(data, ""); data = Pattern.create("\\r?\\n[\\r\\n\\t ]*$", "g").replaceAll(data, ""); return data; } public static String trimLeft(String str) { return str.replaceFirst("^\\s+", ""); } public static String trimRight(String str) { return str.replaceFirst("\\s+$", ""); } /** * Returns the zero or more characters that prefix all of the lines (but see * allowPhantomWhitespace). * @param lines The lines from which to find a common prefix. * @param allowPhantomWhitespace See comment in function body * @return */ public static String getCommonPrefix(String[] lines, boolean allowPhantomWhitespace) { if (lines.length == 0) return ""; /** * allowPhantomWhitespace demands some explanation. Assuming these lines: * * { * "#", * "# hello", * "#", * "# goodbye", * "# hello again" * } * * The result with allowPhantomWhitespace = false would be "#", but with * allowPhantomWhiteSpace = true it would be "# ". Basically phantom * whitespace refers to spots at the end of a line where additional * whitespace would lead to a longer overall prefix but would not change * the visible appearance of the document. */ String prefix = notNull(lines[0]); // Usually the prefix gradually gets shorter and shorter. // whitespaceExpansionAllowed means that the prefix might get longer, // because the prefix as it stands is eligible for phantom whitespace // insertion. This is true iff the prefix is the same length as, or longer // than, all of the lines we have processed. boolean whitespaceExpansionAllowed = allowPhantomWhitespace; for (int i = 1; i < lines.length && prefix.length() > 0; i++) { String line = notNull(lines[i]); int len = whitespaceExpansionAllowed ? Math.max(prefix.length(), line.length()) : allowPhantomWhitespace ? prefix.length() : Math.min(prefix.length(), line.length()); int j; for (j = 0; j < len; j++) { if (j >= prefix.length()) { assert whitespaceExpansionAllowed; if (!isWhitespace(line.charAt(j))) break; continue; } if (j >= line.length()) { assert allowPhantomWhitespace; if (!isWhitespace(prefix.charAt(j))) break; continue; } if (prefix.charAt(j) != line.charAt(j)) { break; } } prefix = j <= prefix.length() ? prefix.substring(0, j) : line.substring(0, j); whitespaceExpansionAllowed = whitespaceExpansionAllowed && (prefix.length() >= line.length()); } return prefix; } private static boolean isWhitespace(char c) { switch (c) { case ' ': case '\t': case '\u00A0': case '\r': case '\n': return true; default: return false; } } public static String pathToTitle(String path) { String val = FileSystemItem.createFile(path).getStem(); val = Pattern.create("\\b[a-z]").replaceAll(val, new ReplaceOperation() { @Override public String replace(Match m) { return m.getValue().toUpperCase(); } }); val = Pattern.create("[-_]").replaceAll(val, " "); return val; } public static String joinStrings(List<String> strings, String separator) { String result = ""; // GWT's exposed Strings.join often makes the compiler barf; do this // manually for (int i = 0; i < strings.size(); i++) { result += strings.get(i); if (i < strings.size() - 1) result += separator; } return result; } // Given an input URL which may be relative, return an absolute URL. Has // no effect on URLs which are already absolute. public static String makeAbsoluteUrl(String inputUrl) { String url = inputUrl; if (!(url.startsWith("http://") || url.startsWith("https://"))) { String thisUrl = Window.Location.getProtocol() + "//" + Window.Location.getHost() + "/"; if (Window.Location.getPath().length() > 0 && !Window.Location.getPath().equals("/")) thisUrl += Window.Location.getPath(); if (!thisUrl.endsWith("/")) thisUrl += "/"; if (url.startsWith("/")) url = url.substring(1); url = thisUrl + url; } return url; } public static String ensureSurroundedWith(String string, char chr) { if (isNullOrEmpty(string)) return "" + chr + chr; String result = string; if (result.charAt(0) != chr) result = chr + result; if (result.charAt(result.length() - 1) != chr) result += chr; return result; } public static String capitalize(String input) { if (input == null || input.length() < 1) return input; return input.substring(0, 1).toUpperCase() + input.substring(1); } public static final native String capitalizeAllWords(String input) /*-{ return input.replace( /(?:^|\s)\S/g, function(x) { return x.toUpperCase(); } ); }-*/; public static int countMatches(String line, char chr) { return line.length() - line.replace(String.valueOf(chr), "").length(); } public static String stripRComment(String string) { boolean inSingleQuotes = false; boolean inDoubleQuotes = false; boolean inQuotes = false; char currentChar = '\0'; char previousChar = '\0'; int commentIndex = string.length(); for (int i = 0; i < string.length(); i++) { currentChar = string.charAt(i); inQuotes = inSingleQuotes || inDoubleQuotes; if (i > 0) { previousChar = string.charAt(i - 1); } if (currentChar == '#' && !inQuotes) { commentIndex = i; break; } if (currentChar == '\'' && !inQuotes) { inSingleQuotes = true; continue; } if (currentChar == '\'' && previousChar != '\\' && inSingleQuotes) { inSingleQuotes = false; continue; } if (currentChar == '"' && !inQuotes) { inDoubleQuotes = true; continue; } if (currentChar == '"' && previousChar != '\\' && inDoubleQuotes) { inDoubleQuotes = false; continue; } } return string.substring(0, commentIndex); } public static String stripBalancedQuotes(String string) { if (string == null) return null; if (string == "") return ""; boolean inSingleQuotes = false; boolean inDoubleQuotes = false; boolean inQuotes = false; int stringStart = 0; char currentChar = '\0'; char previousChar = '\0'; StringBuilder result = new StringBuilder(); for (int i = 0; i < string.length(); i++) { currentChar = string.charAt(i); inQuotes = inSingleQuotes || inDoubleQuotes; if (i > 0) { previousChar = string.charAt(i - 1); } if (currentChar == '\'' && !inQuotes) { inSingleQuotes = true; result.append(string.substring(stringStart, i)); continue; } if (currentChar == '\'' && previousChar != '\\' && inSingleQuotes) { inSingleQuotes = false; stringStart = i + 1; continue; } if (currentChar == '"' && !inQuotes) { inDoubleQuotes = true; result.append(string.substring(stringStart, i)); continue; } if (currentChar == '"' && previousChar != '\\' && inDoubleQuotes) { inDoubleQuotes = false; stringStart = i + 1; continue; } } result.append(string.substring(stringStart, string.length())); return result.toString(); } public static String maskStrings(String string) { return maskStrings(string, 'x'); } public static String maskStrings(String string, char ch) { if (string == null) return null; if (string.length() == 0) return ""; boolean inSingleQuotes = false; boolean inDoubleQuotes = false; boolean inQuotes = false; char currentChar = '\0'; char previousChar = '\0'; StringBuilder result = new StringBuilder(); for (int i = 0; i < string.length(); i++) { currentChar = string.charAt(i); inQuotes = inSingleQuotes || inDoubleQuotes; if (i > 0) { previousChar = string.charAt(i - 1); } if (currentChar == '\'' && !inQuotes) { inSingleQuotes = true; result.append(currentChar); continue; } else if (currentChar == '\'' && previousChar != '\\' && inSingleQuotes) { inSingleQuotes = false; result.append(currentChar); continue; } else if (currentChar == '"' && !inQuotes) { inDoubleQuotes = true; result.append(currentChar); continue; } else if (currentChar == '"' && previousChar != '\\' && inDoubleQuotes) { inDoubleQuotes = false; result.append(currentChar); continue; } if (inSingleQuotes || inDoubleQuotes) result.append(ch); else result.append(currentChar); } return result.toString(); } public static boolean isEndOfLineInRStringState(String string) { if (string == null) return false; if (string == "") return false; boolean inSingleQuotes = false; boolean inDoubleQuotes = false; boolean inQuotes = false; char currentChar = '\0'; char previousChar = '\0'; for (int i = 0; i < string.length(); i++) { currentChar = string.charAt(i); inQuotes = inSingleQuotes || inDoubleQuotes; if (i > 0) { previousChar = string.charAt(i - 1); } if (currentChar == '#' && !inQuotes) { return false; } if (currentChar == '\'' && !inQuotes) { inSingleQuotes = true; continue; } if (currentChar == '\'' && previousChar != '\\' && inSingleQuotes) { inSingleQuotes = false; continue; } if (currentChar == '"' && !inQuotes) { inDoubleQuotes = true; continue; } if (currentChar == '"' && previousChar != '\\' && inDoubleQuotes) { inDoubleQuotes = false; continue; } } return inSingleQuotes || inDoubleQuotes; } public static boolean isSubsequence(String self, String other, boolean caseInsensitive) { return caseInsensitive ? isSubsequence(self.toLowerCase(), other.toLowerCase()) : isSubsequence(self, other); } public static boolean isSubsequence(String self, String other) { final int self_n = self.length(); final int other_n = other.length(); if (other_n > self_n) return false; int self_idx = 0; int other_idx = 0; while (self_idx < self_n) { char selfChar = self.charAt(self_idx); char otherChar = other.charAt(other_idx); if (otherChar == selfChar) { ++other_idx; if (other_idx == other_n) { return true; } } ++self_idx; } return false; } public static int[] subsequenceIndices(String sequence, String query) { int query_n = query.length(); int[] result = new int[query.length()]; int prevMatchIndex = -1; for (int i = 0; i < query_n; i++) { result[i] = sequence.indexOf(query.charAt(i), prevMatchIndex + 1); prevMatchIndex = result[i]; } return result; } public static String getExtension(String string, int dots) { assert dots > 0; int lastDotIndex = -1; if (dots == 1) { lastDotIndex = string.lastIndexOf('.'); } else { String reversed = new StringBuilder(string).reverse().toString(); for (int i = 0; i < dots; i++) { lastDotIndex = reversed.indexOf('.', lastDotIndex); } lastDotIndex = string.length() - lastDotIndex; } return lastDotIndex == -1 || lastDotIndex == string.length() - 1 ? "" : string.substring(lastDotIndex + 1, string.length()); } public static String getExtension(String string) { return getExtension(string, 1); } public static String getToken(String string, int pos, String tokenRegex, boolean expandForward, boolean backOverWhitespace) { if (backOverWhitespace) while (pos > 0 && string.substring(pos - 1, pos).matches("\\s")) --pos; int startPos = Math.max(0, pos - 1); int endPos = Math.min(pos, string.length()); while (startPos >= 0 && string.substring(startPos, startPos + 1).matches(tokenRegex)) --startPos; if (expandForward) while (endPos < string.length() && string.substring(endPos, endPos + 1).matches(tokenRegex)) ++endPos; if (startPos >= endPos) return ""; return string.substring(startPos + 1, endPos); } public static String repeat(String string, int times) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < times; i++) builder.append(string); return builder.toString(); } public static ArrayList<Integer> indicesOf(String string, char ch) { ArrayList<Integer> indices = new ArrayList<Integer>(); int matchIndex = string.indexOf(ch); while (matchIndex != -1) { indices.add(matchIndex); matchIndex = string.indexOf(ch, matchIndex + 1); } return indices; } @SuppressWarnings("deprecation") // GWT emulation only provides isSpace public static boolean isWhitespace(String string) { for (int i = 0; i < string.length(); i++) if (!Character.isSpace(string.charAt(i))) return false; return true; } private static final String[] LABELS = { "B", "KB", "MB", "GB", "TB" }; public static boolean isComplementOf(String self, String other) { return COMPLEMENTS.get(self).equals(other); } private static final HashMap<String, String> makeComplementsMap() { HashMap<String, String> map = new HashMap<String, String>(); map.put("[", "]"); map.put("]", "["); map.put("<", ">"); map.put(">", "<"); map.put("{", "}"); map.put("}", "{"); map.put("(", ")"); map.put(")", "("); return map; } public static final HashMap<String, String> COMPLEMENTS = makeComplementsMap(); private static final NumberFormat FORMAT = NumberFormat.getFormat("0.#"); private static final NumberFormat PRETTY_NUMBER_FORMAT = NumberFormat.getFormat("#,##0.#####"); private static final DateTimeFormat DATE_FORMAT = DateTimeFormat.getFormat("MMM d, yyyy, h:mm a"); }