/* $RCSfile$ * $Author: hansonr $ * $Date: 2007-04-26 16:57:51 -0500 (Thu, 26 Apr 2007) $ * $Revision: 7502 $ * * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017 * for use in SwingJS via transpilation into JavaScript using Java2Script. * * Copyright (C) 2005 The Jmol Development Team * * Contact: jmol-developers@lists.sf.net * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ package javajs.util; import java.lang.reflect.Array; import java.util.Map; import java.util.Map.Entry; import javajs.api.JSONEncodable; /** * a combination of Parsing and Text-related utility classes * * @author hansonr * */ public class PT { public static int parseInt(String str) { return parseIntNext(str, new int[] {0}); } public static int parseIntNext(String str, int[] next) { int cch = str.length(); if (next[0] < 0 || next[0] >= cch) return Integer.MIN_VALUE; return parseIntChecked(str, cch, next); } public static int parseIntChecked(String str, int ichMax, int[] next) { boolean digitSeen = false; int value = 0; int ich = next[0]; if (ich < 0) return Integer.MIN_VALUE; int ch; while (ich < ichMax && isWhiteSpace(str, ich)) ++ich; boolean negative = false; if (ich < ichMax && str.charAt(ich) == 45) { //"-" negative = true; ++ich; } while (ich < ichMax && (ch = str.charAt(ich)) >= 48 && ch <= 57) { value = value * 10 + (ch - 48); digitSeen = true; ++ich; } if (!digitSeen)// || !checkTrailingText(str, ich, ichMax)) value = Integer.MIN_VALUE; else if (negative) value = -value; next[0] = ich; return value; } public static boolean isWhiteSpace(String str, int ich) { char ch; return (ich >= 0 && ((ch = str.charAt(ich)) == ' ' || ch == '\t' || ch == '\n')); } /** * A float parser that is 30% faster than Float.parseFloat(x) and also accepts * x.yD+-n * * @param str * @param ichMax * @param next * pointer; incremented * @param isStrict * @return value or Float.NaN */ public static float parseFloatChecked(String str, int ichMax, int[] next, boolean isStrict) { boolean digitSeen = false; int ich = next[0]; if (isStrict && str.indexOf('\n') != str.lastIndexOf('\n')) return Float.NaN; while (ich < ichMax && isWhiteSpace(str, ich)) ++ich; boolean negative = false; if (ich < ichMax && str.charAt(ich) == '-') { ++ich; negative = true; } // looks crazy, but if we don't do this, Google Closure Compiler will // write code that Safari will misinterpret in a VERY nasty way -- // getting totally confused as to long integers and double values // This is Safari figuring out the values of the numbers on the line (x, y, then z): // ATOM 1241 CD1 LEU A 64 -2.206 36.532 31.576 1.00 60.60 C // e=1408749273 // -e =-1408749273 // ATOM 1241 CD1 LEU A 64 -2.206 36.532 31.576 1.00 60.60 C // e=-1821066134 // e=36.532 // ATOM 1241 CD1 LEU A 64 -2.206 36.532 31.576 1.00 60.60 C // e=-1133871366 // e=31.576 // // "e" values are just before and after the "value = -value" statement. int ch = 0; float ival = 0f; float ival2 = 0f; while (ich < ichMax && (ch = str.charAt(ich)) >= 48 && ch <= 57) { ival = (ival * 10f) + (ch - 48)*1f; ++ich; digitSeen = true; } boolean isDecimal = false; int iscale = 0; int nzero = (ival == 0 ? -1 : 0); if (ch == '.') { isDecimal = true; while (++ich < ichMax && (ch = str.charAt(ich)) >= 48 && ch <= 57) { digitSeen = true; if (nzero < 0) { if (ch == 48) { nzero--; continue; } nzero = -nzero; } if (iscale < decimalScale.length) { ival2 = (ival2 * 10f) + (ch - 48)*1f; iscale++; } } } float value; // Safari breaks here intermittently converting integers to floats if (!digitSeen) { value = Float.NaN; } else if (ival2 > 0) { value = ival2 * decimalScale[iscale - 1]; if (nzero > 1) { if (nzero - 2 < decimalScale.length) { value *= decimalScale[nzero - 2]; } else { value *= Math.pow(10, 1 - nzero); } } else { value += ival; } } else { value = ival; } boolean isExponent = false; if (ich < ichMax && (ch == 69 || ch == 101 || ch == 68)) { // E e D isExponent = true; if (++ich >= ichMax) return Float.NaN; ch = str.charAt(ich); if ((ch == '+') && (++ich >= ichMax)) return Float.NaN; next[0] = ich; int exponent = parseIntChecked(str, ichMax, next); if (exponent == Integer.MIN_VALUE) return Float.NaN; if (exponent > 0 && exponent <= tensScale.length) value *= tensScale[exponent - 1]; else if (exponent < 0 && -exponent <= decimalScale.length) value *= decimalScale[-exponent - 1]; else if (exponent != 0) value *= Math.pow(10, exponent); } else { next[0] = ich; // the exponent code finds its own ichNextParse } // believe it or not, Safari reports the long-equivalent of the // float value here, then later the float value, after no operation! if (negative) value = -value; if (value == Float.POSITIVE_INFINITY) value = Float.MAX_VALUE; return (!isStrict || (!isExponent || isDecimal) && checkTrailingText(str, next[0], ichMax) ? value : Float.NaN); } public final static float[] tensScale = { 10f, 100f, 1000f, 10000f, 100000f, 1000000f }; public final static float[] decimalScale = { 0.1f, 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f, 0.0000001f, 0.00000001f, 0.000000001f }; public static boolean checkTrailingText(String str, int ich, int ichMax) { //number must be pure -- no additional characters other than white space or ; char ch; while (ich < ichMax && (isWhitespace(ch = str.charAt(ich)) || ch == ';')) ++ich; return (ich == ichMax); } public static float[] parseFloatArray(String str) { return parseFloatArrayNext(str, new int[1], null, null, null); } public static int parseFloatArrayInfested(String[] tokens, float[] data) { int len = data.length; int nTokens = tokens.length; int n = 0; int max = 0; for (int i = 0; i >= 0 && i < len && n < nTokens; i++) { float f; while (Float.isNaN(f = parseFloat(tokens[n++])) && n < nTokens) { } if (!Float.isNaN(f)) data[(max = i)] = f; if (n == nTokens) break; } return max + 1; } /** * @param str * @param next * @param f * @param strStart or null * @param strEnd or null * @return array of float values * */ public static float[] parseFloatArrayNext(String str, int[] next, float[] f, String strStart, String strEnd) { int n = 0; int pt = next[0]; if (pt >= 0) { if (strStart != null) { int p = str.indexOf(strStart, pt); if (p >= 0) next[0] = p + strStart.length(); } str = str.substring(next[0]); pt = (strEnd == null ? -1 : str.indexOf(strEnd)); if (pt < 0) pt = str.length(); else str = str.substring(0, pt); next[0] += pt + 1; String[] tokens = getTokens(str); if (f == null) f = new float[tokens.length]; n = parseFloatArrayInfested(tokens, f); } if (f == null) return new float[0]; for (int i = n; i < f.length; i++) f[i] = Float.NaN; return f; } public static float parseFloatRange(String str, int ichMax, int[] next) { int cch = str.length(); if (ichMax > cch) ichMax = cch; if (next[0] < 0 || next[0] >= ichMax) return Float.NaN; return parseFloatChecked(str, ichMax, next, false); } public static float parseFloatNext(String str, int[] next) { int cch = (str == null ? -1 : str.length()); return (next[0] < 0 || next[0] >= cch ? Float.NaN : parseFloatChecked(str, cch, next, false)); } public static float parseFloatStrict(String str) { // checks trailing characters and does not allow "1E35" to be float int cch = str.length(); if (cch == 0) return Float.NaN; return parseFloatChecked(str, cch, new int[] {0}, true); } public static float parseFloat(String str) { return parseFloatNext(str, new int[] {0}); } public static int parseIntRadix(String s, int i) throws NumberFormatException { /** * * JavaScript uses parseIntRadix * * @j2sNative * * return Integer.parseIntRadix(s, i); * */ { return Integer.parseInt(s, i); } } public static String[] getTokens(String line) { return getTokensAt(line, 0); } public static String parseToken(String str) { return parseTokenNext(str, new int[] {0}); } public static String parseTrimmed(String str) { return parseTrimmedRange(str, 0, str.length()); } public static String parseTrimmedAt(String str, int ichStart) { return parseTrimmedRange(str, ichStart, str.length()); } public static String parseTrimmedRange(String str, int ichStart, int ichMax) { int cch = str.length(); if (ichMax < cch) cch = ichMax; if (cch < ichStart) return ""; return parseTrimmedChecked(str, ichStart, cch); } public static String[] getTokensAt(String line, int ich) { if (line == null) return null; int cchLine = line.length(); if (ich < 0 || ich > cchLine) return null; int tokenCount = countTokens(line, ich); String[] tokens = new String[tokenCount]; int[] next = new int[1]; next[0] = ich; for (int i = 0; i < tokenCount; ++i) tokens[i] = parseTokenChecked(line, cchLine, next); return tokens; } public static int countChar(String line, char c) { int n = 0; for (int i = line.lastIndexOf(c) + 1; --i >= 0;) if (line.charAt(i) == c) n++; return n; } public static int countTokens(String line, int ich) { int tokenCount = 0; if (line != null) { int ichMax = line.length(); while (true) { while (ich < ichMax && isWhiteSpace(line, ich)) ++ich; if (ich == ichMax) break; ++tokenCount; do { ++ich; } while (ich < ichMax && !isWhiteSpace(line, ich)); } } return tokenCount; } public static String parseTokenNext(String str, int[] next) { int cch = str.length(); return (next[0] < 0 || next[0] >= cch ? null : parseTokenChecked(str, cch, next)); } public static String parseTokenRange(String str, int ichMax, int[] next) { int cch = str.length(); if (ichMax > cch) ichMax = cch; return (next[0] < 0 || next[0] >= ichMax ? null : parseTokenChecked(str, ichMax, next)); } public static String parseTokenChecked(String str, int ichMax, int[] next) { int ich = next[0]; while (ich < ichMax && isWhiteSpace(str, ich)) ++ich; int ichNonWhite = ich; while (ich < ichMax && !isWhiteSpace(str, ich)) ++ich; next[0] = ich; return (ichNonWhite == ich ? null : str.substring(ichNonWhite, ich)); } public static String parseTrimmedChecked(String str, int ich, int ichMax) { while (ich < ichMax && isWhiteSpace(str, ich)) ++ich; int ichLast = ichMax - 1; while (ichLast >= ich && isWhiteSpace(str, ichLast)) --ichLast; return (ichLast < ich ? "" : str.substring(ich, ichLast + 1)); } // public static double dVal(String s) throws NumberFormatException { // /** // * @j2sNative // * // * if(s==null) // * throw new NumberFormatException("null"); // * var d=parseFloat(s); // * if(isNaN(d)) // * throw new NumberFormatException("Not a Number : "+s); // * return d // * // */ // { // return Double.valueOf(s).doubleValue(); // } // } // // public static float fVal(String s) throws NumberFormatException { // /** // * @j2sNative // * // * return this.dVal(s); // */ // { // // return Float.parseFloat(s); // } // } public static int parseIntRange(String str, int ichMax, int[] next) { int cch = str.length(); if (ichMax > cch) ichMax = cch; return (next[0] < 0 || next[0] >= ichMax ? Integer.MIN_VALUE : parseIntChecked(str, ichMax, next)); } /** * parses a string array for floats. Returns NaN for nonfloats. * * @param tokens the strings to parse * @param data the array to fill */ public static void parseFloatArrayData(String[] tokens, float[] data) { parseFloatArrayDataN(tokens, data, data.length); } /** * parses a string array for floats. Returns NaN for nonfloats or missing data. * * @param tokens the strings to parse * @param data the array to fill * @param nData the number of elements */ public static void parseFloatArrayDataN(String[] tokens, float[] data, int nData) { for (int i = nData; --i >= 0;) data[i] = (i >= tokens.length ? Float.NaN : parseFloat(tokens[i])); } /** * * proper splitting, even for Java 1.3 -- if the text ends in the run, * no new line is appended. * * @param text * @param run * @return String array */ public static String[] split(String text, String run) { if (text.length() == 0) return new String[0]; int n = 1; int i = text.indexOf(run); String[] lines; int runLen = run.length(); if (i < 0 || runLen == 0) { lines = new String[1]; lines[0] = text; return lines; } int len = text.length() - runLen; for (; i >= 0 && i < len; n++) i = text.indexOf(run, i + runLen); lines = new String[n]; i = 0; int ipt = 0; int pt = 0; for (; (ipt = text.indexOf(run, i)) >= 0 && pt + 1 < n;) { lines[pt++] = text.substring(i, ipt); i = ipt + runLen; } if (text.indexOf(run, len) != len) len += runLen; lines[pt] = text.substring(i, len); return lines; } public final static float FLOAT_MIN_SAFE = 2E-45f; // Float.MIN_VALUE (1.45E-45) is not reliable with JavaScript because of the float/double difference there /// general static string-parsing class /// // next[0] tracks the pointer within the string so these can all be static. // but the methods parseFloat, parseInt, parseToken, parseTrimmed, and getTokens do not require this. // public static String concatTokens(String[] tokens, int iFirst, int iEnd) { // String str = ""; // String sep = ""; // for (int i = iFirst; i < iEnd; i++) { // if (i < tokens.length) { // str += sep + tokens[i]; // sep = " "; // } // } // return str; // } public static String getQuotedStringAt(String line, int ipt0) { int[] next = new int[] { ipt0 }; return getQuotedStringNext(line, next); } /** * * @param line * @param next passes [current pointer] * @return quoted string -- does NOT unescape characters */ public static String getQuotedStringNext(String line, int[] next) { int i = next[0]; if (i < 0 || (i = line.indexOf("\"", i)) < 0) return ""; int pt = i + 1; int len = line.length(); while (++i < len && line.charAt(i) != '"') if (line.charAt(i) == '\\') i++; next[0] = i + 1; return line.substring(pt, i); } /** * single- or double-quoted string or up to the first space -- like HTML5 * not case-sensitive * * @param line * @param key * @return attribute */ public static String getQuotedOrUnquotedAttribute(String line, String key) { if (line == null || key == null) return null; int pt = line.toLowerCase().indexOf(key.toLowerCase() + "="); if (pt < 0 || (pt = pt + key.length() + 1) >= line.length()) return ""; char c = line.charAt(pt); switch (c) { case '\'': case '"': pt++; break; default: c = ' '; line += " "; } int pt1 = line.indexOf(c, pt); return (pt1 < 0 ? null : line.substring(pt, pt1)); } /** * CSV format -- escaped quote is "" WITHIN "..." * * * @param line * @param next int[2] filled with [ptrQuote1, ptrAfterQuote2] * next[1] will be -1 if unmatched quotes are found (continuation on next line) * @return unescaped string or null */ public static String getCSVString(String line, int[] next) { int i = next[1]; if (i < 0 || (i = line.indexOf("\"", i)) < 0) return null; int pt = next[0] = i; int len = line.length(); boolean escaped = false; boolean haveEscape = false; while (++i < len && (line.charAt(i) != '"' || (escaped = (i + 1 < len && line.charAt(i + 1) == '"')))) if (escaped) { escaped = false; haveEscape = true; i++; } if (i >= len) { next[1] = -1; return null; // unmatched } next[1] = i + 1; String s = line.substring(pt + 1, i); return (haveEscape ? rep(rep(s, "\"\"", "\0"), "\0","\"") : s); } public static boolean isOneOf(String key, String semiList) { if (semiList.length() == 0) return false; if (semiList.charAt(0) != ';') semiList = ";" + semiList + ";"; return key.indexOf(";") < 0 && semiList.indexOf(';' + key + ';') >= 0; } public static String getQuotedAttribute(String info, String name) { int i = info.indexOf(name + "="); return (i < 0 ? null : getQuotedStringAt(info, i)); } public static float approx(float f, float n) { return Math.round (f * n) / n; } /** * Does a clean ITERATIVE replace of strFrom in str with strTo. * Thus, rep("Testttt", "tt","t") becomes "Test". * * @param str * @param strFrom * @param strTo * @return replaced string */ public static String rep(String str, String strFrom, String strTo) { if (str == null || strFrom.length() == 0 || str.indexOf(strFrom) < 0) return str; boolean isOnce = (strTo.indexOf(strFrom) >= 0); do { str = str.replace(strFrom, strTo); } while (!isOnce && str.indexOf(strFrom) >= 0); return str; } public static String formatF(float value, int width, int precision, boolean alignLeft, boolean zeroPad) { return formatS(DF.formatDecimal(value, precision), width, 0, alignLeft, zeroPad); } /** * * @param value * @param width * @param precision * @param alignLeft * @param zeroPad * @param allowOverflow IGNORED * @return formatted string */ public static String formatD(double value, int width, int precision, boolean alignLeft, boolean zeroPad, boolean allowOverflow) { return formatS(DF.formatDecimal((float)value, -1 - precision), width, 0, alignLeft, zeroPad); } /** * * @param value * @param width number of columns * @param precision precision > 0 ==> precision = number of characters max from left * precision < 0 ==> -1 - precision = number of char. max from right * @param alignLeft * @param zeroPad generally for numbers turned strings * @return formatted string */ public static String formatS(String value, int width, int precision, boolean alignLeft, boolean zeroPad) { if (value == null) return ""; int len = value.length(); if (precision != Integer.MAX_VALUE && precision > 0 && precision < len) value = value.substring(0, precision); else if (precision < 0 && len + precision >= 0) value = value.substring(len + precision + 1); int padLength = width - value.length(); if (padLength <= 0) return value; boolean isNeg = (zeroPad && !alignLeft && value.charAt(0) == '-'); char padChar = (zeroPad ? '0' : ' '); char padChar0 = (isNeg ? '-' : padChar); SB sb = new SB(); if (alignLeft) sb.append(value); sb.appendC(padChar0); for (int i = padLength; --i > 0;) // this is correct, not >= 0 sb.appendC(padChar); if (!alignLeft) sb.append(isNeg ? padChar + value.substring(1) : value); return sb.toString(); } /** * Does a clean replace of any of the characters in str with chrTo * If strTo contains strFrom, then only a single pass is done. * Otherwise, multiple passes are made until no more replacements can be made. * * @param str * @param strFrom * @param chTo * @return replaced string */ public static String replaceWithCharacter(String str, String strFrom, char chTo) { if (str == null) return null; for (int i = strFrom.length(); --i >= 0;) str = str.replace(strFrom.charAt(i), chTo); return str; } /** * Does a clean replace of any of the characters in str with strTo * If strTo contains strFrom, then only a single pass is done. * Otherwise, multiple passes are made until no more replacements can be made. * * @param str * @param strFrom * @param strTo * @return replaced string */ public static String replaceAllCharacters(String str, String strFrom, String strTo) { for (int i = strFrom.length(); --i >= 0;) { String chFrom = strFrom.substring(i, i + 1); str = rep(str, chFrom, strTo); } return str; } public static String trim(String str, String chars) { if (str == null || str.length() == 0) return str; if (chars.length() == 0) return str.trim(); int len = str.length(); int k = 0; while (k < len && chars.indexOf(str.charAt(k)) >= 0) k++; int m = str.length() - 1; while (m > k && chars.indexOf(str.charAt(m)) >= 0) m--; return str.substring(k, m + 1); } public static String trimQuotes(String value) { return (value != null && value.length() > 1 && value.startsWith("\"") && value.endsWith("\"") ? value.substring(1, value.length() - 1) : value); } public static boolean isNonStringPrimitive(Object info) { // note that we don't use Double, Float, or Integer here // because in JavaScript those would be false for unwrapped primitives // coming from equivalent of Array.get() // Strings will need their own escaped processing return info instanceof Number || info instanceof Boolean; } // private static Object arrayGet(Object info, int i) { // /** // * // * Note that info will be a primitive in JavaScript // * but a wrapped primitive in Java. // * // * @j2sNative // * // * return info[i]; // */ // { // return Array.get(info, i); // } // } // @SuppressWarnings("unchecked") public static String toJSON(String infoType, Object info) { if (info == null) return packageJSON(infoType, null); if (isNonStringPrimitive(info)) return packageJSON(infoType, info.toString()); String s = null; SB sb = null; while (true) { if (info instanceof String) { s = (String) info; /** * @j2sNative * * if (typeof s == "undefined") s = "null" * */ {} if (s.indexOf("{\"") != 0) { //don't doubly fix JSON strings when retrieving status // what about \1 \2 \3 etc.? s = esc(s); } break; } if (info instanceof JSONEncodable) { // includes javajs.util.BS, org.jmol.script.SV if ((s = ((JSONEncodable) info).toJSON()) == null) s = "null"; // perhaps a list has a null value (group3List, for example) break; } sb = new SB(); if (info instanceof Map) { sb.append("{ "); String sep = ""; for (String key : ((Map) info).keySet()) { sb.append(sep).append( packageJSON(key, toJSON(null, ((Map) info).get(key)))); sep = ","; } sb.append(" }"); break; } if (info instanceof Lst) { sb.append("[ "); int n = ((Lst) info).size(); for (int i = 0; i < n; i++) { if (i > 0) sb.appendC(','); sb.append(toJSON(null, ((Lst) info).get(i))); } sb.append(" ]"); break; } if (info instanceof M34) { // M4 extends M3 int len = (info instanceof M4 ? 4 : 3); float[] x = new float[len]; M34 m = (M34) info; sb.appendC('['); for (int i = 0; i < len; i++) { if (i > 0) sb.appendC(','); m.getRow(i, x); sb.append(toJSON(null, x)); } sb.appendC(']'); break; } s = nonArrayString(info); if (s == null) { sb.append("["); int n = AU.getLength(info); for (int i = 0; i < n; i++) { if (i > 0) sb.appendC(','); sb.append(toJSON(null, Array.get(info, i))); } sb.append("]"); break; } info = info.toString(); } return packageJSON(infoType, (s == null ? sb.toString() : s)); } /** * Checks to see if an object is an array (including typed arrays), and if it is, returns null; * otherwise it returns the string equivalent of that object. * * @param x * @return String or null */ public static String nonArrayString(Object x) { /** * @j2sNative * * return (x.constructor == Array || x.BYTES_PER_ELEMENT ? null : x.toString()); * */ { try { Array.getLength(x); return null; } catch (Exception e) { return x.toString(); } } } public static String byteArrayToJSON(byte[] data) { SB sb = new SB(); sb.append("["); int n = data.length; for (int i = 0; i < n; i++) { if (i > 0) sb.appendC(','); sb.appendI(data[i] & 0xFF); } sb.append("]"); return sb.toString(); } public static String packageJSON(String infoType, String info) { return (infoType == null ? info : "\"" + infoType + "\": " + info); } public static String escapeUrl(String url) { url = rep(url, "\n", ""); url = rep(url, "%", "%25"); url = rep(url, "#", "%23"); url = rep(url, "[", "%5B"); url = rep(url, "\\", "%5C"); url = rep(url, "]", "%5D"); url = rep(url, " ", "%20"); return url; } private final static String escapable = "\\\\\tt\rr\nn\"\""; public static String esc(String str) { if (str == null || str.length() == 0) return "\"\""; boolean haveEscape = false; int i = 0; for (; i < escapable.length(); i += 2) if (str.indexOf(escapable.charAt(i)) >= 0) { haveEscape = true; break; } if (haveEscape) while (i < escapable.length()) { int pt = -1; char ch = escapable.charAt(i++); char ch2 = escapable.charAt(i++); SB sb = new SB(); int pt0 = 0; while ((pt = str.indexOf(ch, pt + 1)) >= 0) { sb.append(str.substring(pt0, pt)).appendC('\\').appendC(ch2); pt0 = pt + 1; } sb.append(str.substring(pt0, str.length())); str = sb.toString(); } return "\"" + escUnicode(str) + "\""; } public static String escUnicode(String str) { for (int i = str.length(); --i >= 0;) if (str.charAt(i) > 0x7F) { String s = "0000" + Integer.toHexString(str.charAt(i)); str = str.substring(0, i) + "\\u" + s.substring(s.length() - 4) + str.substring(i + 1); } return str; } /** * ensures that a float turned to string has a decimal point * * @param f * @return string version of float */ public static String escF(float f) { String sf = "" + f; /** * @j2sNative * * if (sf.indexOf(".") < 0 && sf.indexOf("e") < 0) * sf += ".0"; */ { } return sf; } public static String join(String[] s, char c, int i0) { if (s.length < i0) return null; SB sb = new SB(); sb.append(s[i0++]); for (int i = i0; i < s.length; i++) sb.appendC(c).append(s[i]); return sb.toString(); } /** * a LIKE "x" a is a string and equals x * * a LIKE "*x" a is a string and ends with x * * a LIKE "x*" a is a string and starts with x * * a LIKE "*x*" a is a string and contains x * * @param a * @param b * @return a LIKE b */ public static boolean isLike(String a, String b) { boolean areEqual = a.equals(b); if (areEqual) return true; boolean isStart = b.startsWith("*"); boolean isEnd = b.endsWith("*"); return (!isStart && !isEnd) ? areEqual : isStart && isEnd ? b.length() == 1 || a.contains(b.substring(1, b.length() - 1)) : isStart ? a.endsWith(b.substring(1)) : a.startsWith(b.substring(0, b.length() - 1)); } public static Object getMapValueNoCase(Map h, String key) { if ("this".equals(key)) return h; Object val = h.get(key); if (val == null) for (Entry e : h.entrySet()) if (e.getKey().equalsIgnoreCase(key)) return e.getValue(); return val; } public static String clean(String s) { return rep(replaceAllCharacters(s, " \t\n\r", " "), " ", " ").trim(); } /** * * fdup duplicates p or q formats for formatCheck * and the format() function. * * @param f * @param pt * @param n * @return %3.5q%3.5q%3.5q%3.5q or %3.5p%3.5p%3.5p */ public static String fdup(String f, int pt, int n) { char ch; int count = 0; for (int i = pt; --i >= 1; ) { if (isDigit(ch = f.charAt(i))) continue; switch (ch) { case '.': if (count++ != 0) return f; continue; case '-': if (i != 1 && f.charAt(i - 1) != '.') return f; continue; default: return f; } } String s = f.substring(0, pt + 1); SB sb = new SB(); for (int i = 0; i < n; i++) sb.append(s); sb.append(f.substring(pt + 1)); return sb.toString(); } /** * generic string formatter based on formatLabel in Atom * * * @param strFormat .... %width.precisionKEY.... * @param key any string to match * @param strT replacement string or null * @param floatT replacement float or Float.NaN * @param doubleT replacement double or Double.NaN -- for exponential * @param doOne mimic sprintf * @return formatted string */ private static String formatString(String strFormat, String key, String strT, float floatT, double doubleT, boolean doOne) { if (strFormat == null) return null; if ("".equals(strFormat)) return ""; int len = key.length(); if (strFormat.indexOf("%") < 0 || len == 0 || strFormat.indexOf(key) < 0) return strFormat; String strLabel = ""; int ich, ichPercent, ichKey; for (ich = 0; (ichPercent = strFormat.indexOf('%', ich)) >= 0 && (ichKey = strFormat.indexOf(key, ichPercent + 1)) >= 0;) { if (ich != ichPercent) strLabel += strFormat.substring(ich, ichPercent); ich = ichPercent + 1; if (ichKey > ichPercent + 6) { strLabel += '%'; continue;//%12.10x } try { boolean alignLeft = false; if (strFormat.charAt(ich) == '-') { alignLeft = true; ++ich; } boolean zeroPad = false; if (strFormat.charAt(ich) == '0') { zeroPad = true; ++ich; } char ch; int width = 0; while ((ch = strFormat.charAt(ich)) >= '0' && (ch <= '9')) { width = (10 * width) + (ch - '0'); ++ich; } int precision = Integer.MAX_VALUE; boolean isExponential = false; if (strFormat.charAt(ich) == '.') { ++ich; if ((ch = strFormat.charAt(ich)) == '-') { isExponential = (strT == null); ++ich; } if ((ch = strFormat.charAt(ich)) >= '0' && ch <= '9') { precision = ch - '0'; ++ich; } if (isExponential) precision = -precision; } String st = strFormat.substring(ich, ich + len); if (!st.equals(key)) { ich = ichPercent + 1; strLabel += '%'; continue; } ich += len; if (!Float.isNaN(floatT)) // 'f' strLabel += formatF(floatT, width, precision, alignLeft, zeroPad); else if (strT != null) // 'd' 'i' or 's' strLabel += formatS(strT, width, precision, alignLeft, zeroPad); else if (!Double.isNaN(doubleT)) // 'e' strLabel += formatD(doubleT, width, precision - 1, alignLeft, zeroPad, true); if (doOne) break; } catch (IndexOutOfBoundsException ioobe) { ich = ichPercent; break; } } strLabel += strFormat.substring(ich); //if (strLabel.length() == 0) //return null; return strLabel; } public static String formatStringS(String strFormat, String key, String strT) { return formatString(strFormat, key, strT, Float.NaN, Double.NaN, false); } public static String formatStringF(String strFormat, String key, float floatT) { return formatString(strFormat, key, null, floatT, Double.NaN, false); } public static String formatStringI(String strFormat, String key, int intT) { return formatString(strFormat, key, "" + intT, Float.NaN, Double.NaN, false); } /** * sprintf emulation uses (almost) c++ standard string formats * * 's' string 'i' or 'd' integer, 'e' double, 'f' float, 'p' point3f 'q' * quaternion/plane/axisangle with added "i" (equal to the insipid "d" -- * digits?) * * @param strFormat * @param list * a listing of what sort of data will be found in Object[] values, in * order: s string, f float, i integer, d double, p point3f, q * quaternion/point4f, S String[], F float[], I int[], and D double[] * @param values * Object[] containing above types * @return formatted string */ public static String sprintf(String strFormat, String list, Object[] values) { if (values == null) return strFormat; int n = list.length(); if (n == values.length) try { for (int o = 0; o < n; o++) { if (values[o] == null) continue; switch (list.charAt(o)) { case 's': strFormat = formatString(strFormat, "s", (String) values[o], Float.NaN, Double.NaN, true); break; case 'f': strFormat = formatString(strFormat, "f", null, ((Float) values[o]) .floatValue(), Double.NaN, true); break; case 'i': strFormat = formatString(strFormat, "d", "" + values[o], Float.NaN, Double.NaN, true); strFormat = formatString(strFormat, "i", "" + values[o], Float.NaN, Double.NaN, true); break; case 'd': strFormat = formatString(strFormat, "e", null, Float.NaN, ((Double) values[o]).doubleValue(), true); break; case 'p': T3 pVal = (T3) values[o]; strFormat = formatString(strFormat, "p", null, pVal.x, Double.NaN, true); strFormat = formatString(strFormat, "p", null, pVal.y, Double.NaN, true); strFormat = formatString(strFormat, "p", null, pVal.z, Double.NaN, true); break; case 'q': T4 qVal = (T4) values[o]; strFormat = formatString(strFormat, "q", null, qVal.x, Double.NaN, true); strFormat = formatString(strFormat, "q", null, qVal.y, Double.NaN, true); strFormat = formatString(strFormat, "q", null, qVal.z, Double.NaN, true); strFormat = formatString(strFormat, "q", null, qVal.w, Double.NaN, true); break; case 'S': String[] sVal = (String[]) values[o]; for (int i = 0; i < sVal.length; i++) strFormat = formatString(strFormat, "s", sVal[i], Float.NaN, Double.NaN, true); break; case 'F': float[] fVal = (float[]) values[o]; for (int i = 0; i < fVal.length; i++) strFormat = formatString(strFormat, "f", null, fVal[i], Double.NaN, true); break; case 'I': int[] iVal = (int[]) values[o]; for (int i = 0; i < iVal.length; i++) strFormat = formatString(strFormat, "d", "" + iVal[i], Float.NaN, Double.NaN, true); for (int i = 0; i < iVal.length; i++) strFormat = formatString(strFormat, "i", "" + iVal[i], Float.NaN, Double.NaN, true); break; case 'D': double[] dVal = (double[]) values[o]; for (int i = 0; i < dVal.length; i++) strFormat = formatString(strFormat, "e", null, Float.NaN, dVal[i], true); } } return rep(strFormat, "%%", "%"); } catch (Exception e) { // } System.out.println("TextFormat.sprintf error " + list + " " + strFormat); return rep(strFormat, "%", "?"); } /** * * formatCheck checks p and q formats and duplicates if necessary * "%10.5p xxxx" ==> "%10.5p%10.5p%10.5p xxxx" * * @param strFormat * @return f or dupicated format */ public static String formatCheck(String strFormat) { if (strFormat == null || strFormat.indexOf('p') < 0 && strFormat.indexOf('q') < 0) return strFormat; strFormat = rep(strFormat, "%%", "\1"); strFormat = rep(strFormat, "%p", "%6.2p"); strFormat = rep(strFormat, "%q", "%6.2q"); String[] format = split(strFormat, "%"); SB sb = new SB(); sb.append(format[0]); for (int i = 1; i < format.length; i++) { String f = "%" + format[i]; int pt; if (f.length() >= 3) { if ((pt = f.indexOf('p')) >= 0) f = fdup(f, pt, 3); if ((pt = f.indexOf('q')) >= 0) f = fdup(f, pt, 4); } sb.append(f); } return sb.toString().replace('\1', '%'); } public static void leftJustify(SB s, String s1, String s2) { s.append(s2); int n = s1.length() - s2.length(); if (n > 0) s.append(s1.substring(0, n)); } public static void rightJustify(SB s, String s1, String s2) { int n = s1.length() - s2.length(); if (n > 0) s.append(s1.substring(0, n)); s.append(s2); } public static String safeTruncate(float f, int n) { if (f > -0.001 && f < 0.001) f = 0; return (f + " ").substring(0,n); } public static boolean isWild(String s) { return s != null && (s.indexOf("*") >= 0 || s.indexOf("?") >= 0); } /** * A general non-regex (for performance) text matcher that utilizes ? and *. * * ??? means "at most three" characters if at beginning or end; * "exactly three" otherwise * \1 in search is a stand-in for actual ? * * @param search * the string to search * @param match * the match string * @param checkStar * @param allowInitialStar * @return true if found */ public static boolean isMatch(String search, String match, boolean checkStar, boolean allowInitialStar) { // search == match --> true if (search.equals(match)) return true; int mLen = match.length(); // match == "" --> false if (mLen == 0) return false; boolean isStar0 = (checkStar && allowInitialStar ? match.charAt(0) == '*' : false); // match == "*" --> true if (mLen == 1 && isStar0) return true; boolean isStar1 = (checkStar && match.endsWith("*")); boolean haveQ = (match.indexOf('?') >= 0); // match == "**" --> true // match == "*xxx*" --> search contains "xxx" // match == "*xxx" --> search ends with "xxx" // match == "xxx*" --> search starts with "xxx" if (!haveQ) { if (isStar0) return (isStar1 ? (mLen < 3 || search.indexOf(match.substring(1, mLen - 1)) >= 0) : search.endsWith(match.substring(1))); else if (isStar1) return search.startsWith(match.substring(0, mLen - 1)); } int sLen = search.length(); // pad match with "?" -- same as * String qqqq = "????"; int nq = 4; while (nq < sLen) { qqqq += qqqq; nq += 4; } if (checkStar) { if (isStar0) { match = qqqq + match.substring(1); mLen += nq - 1; } if (isStar1) { match = match.substring(0, mLen - 1) + qqqq; mLen += nq - 1; } } // length of match < length of search --> false if (mLen < sLen) return false; // -- each ? matches ONE character if not at end // -- extra ? at end ignored // (allowInitialStar == true) // -- extra ? at beginning reduced to match length int ich = 0; while (mLen > sLen) { if (allowInitialStar && match.charAt(ich) == '?') { ++ich; } else if (match.charAt(ich + mLen - 1) != '?') { return false; } --mLen; } // both are effectively same length now. // \1 is stand-in for "?" for (int i = sLen; --i >= 0;) { char chm = match.charAt(ich + i); if (chm == '?') continue; char chs = search.charAt(i); if (chm != chs && (chm != '\1' || chs != '?')) return false; } return true; } public static String replaceQuotedStrings(String s, Lst list, Lst newList) { int n = list.size(); for (int i = 0; i < n; i++) { String name = list.get(i); String newName = newList.get(i); if (!newName.equals(name)) s = rep(s, "\"" + name + "\"", "\"" + newName + "\""); } return s; } public static String replaceStrings(String s, Lst list, Lst newList) { int n = list.size(); for (int i = 0; i < n; i++) { String name = list.get(i); String newName = newList.get(i); if (!newName.equals(name)) s = rep(s, name, newName); } return s; } public static boolean isDigit(char ch) { // just way simpler code than Character.isDigit(ch); int c = ch; return (48 <= c && c <= 57); } public static boolean isUpperCase(char ch) { int c = ch; return (65 <= c && c <= 90); } public static boolean isLowerCase(char ch) { int c = ch; return (97 <= c && c <= 122); } public static boolean isLetter(char ch) { // just way simpler code than Character.isLetter(ch); int c = ch; return (65 <= c && c <= 90 || 97 <= c && c <= 122); } public static boolean isLetterOrDigit(char ch) { // just way simpler code than Character.isLetterOrDigit(ch); int c = ch; return (65 <= c && c <= 90 || 97 <= c && c <= 122 || 48 <= c && c <= 57); } public static boolean isWhitespace(char ch) { int c = ch; return (c >= 0x1c && c <= 0x20 || c >= 0x9 && c <= 0xd); } public static final float FRACTIONAL_PRECISION = 100000f; public static final float CARTESIAN_PRECISION = 10000f; public static void fixPtFloats(T3 pt, float f) { //this will equate float and double as long as -256 <= x <= 256 pt.x = Math.round(pt.x * f) / f; pt.y = Math.round(pt.y * f) / f; pt.z = Math.round(pt.z * f) / f; } public static double fixDouble(double d, double f) { return Math.round(d * f) / f; } /** * parse a float or "float/float" * @param s * @return a/b */ public static float parseFloatFraction(String s) { int pt = s.indexOf("/"); return (pt < 0 ? parseFloat(s) : parseFloat(s.substring(0, pt)) / parseFloat(s.substring(pt + 1))); } //static { // // double d = 790.8999998888; // float x = 790.8999998888f; // for (int i = 0; i < 50; i++) { // System.out.println(x + " " + d); // System.out.println(Math.round(x * 100000) / 100000f); // System.out.println(Math.round(d * 100000) / 100000.); // System.out.println(Math.round(x * 10000) / 10000f); // System.out.println(Math.round(d * 10000) / 10000.); // x+=1; // d+=1; // } // System.out.println(100.123456789f); //} // static { // long t; // char c = '0'; // t = System.currentTimeMillis(); // for (int i = 0; i < 10000000; i++) { // boolean b = PT.isUpperCase(c); // } // System.out.println(System.currentTimeMillis() - t); // // t = System.currentTimeMillis(); // for (int i = 0; i < 10000000; i++) { // boolean b = Character.isUpperCase(c); // } // System.out.println(System.currentTimeMillis() - t); // // t = System.currentTimeMillis(); // for (int i = 0; i < 10000000; i++) { // boolean b = PT.isUpperCase(c); // } // System.out.println(System.currentTimeMillis() - t); // // System.out.println("PT test"); // } }