X-Git-Url: http://source.jalview.org/gitweb/?p=jalviewjs.git;a=blobdiff_plain;f=src%2Fjavajs%2Futil%2FPT.java;fp=src%2Fjavajs%2Futil%2FPT.java;h=e6a27b41a6c37d1aafafd88857dbf495ca38177d;hp=fa9cbe3b77d99c5a9b2da2316f7b5535e55a1931;hb=b9b7a352eee79b7764c3b09c9d19663075061d8c;hpb=7301a2415adab88038b291fc54caeeb3a5a47a44 diff --git a/src/javajs/util/PT.java b/src/javajs/util/PT.java index fa9cbe3..e6a27b4 100644 --- a/src/javajs/util/PT.java +++ b/src/javajs/util/PT.java @@ -1,1540 +1,1540 @@ -/* $RCSfile$ - * $Author: hansonr $ - * $Date: 2007-04-26 16:57:51 -0500 (Thu, 26 Apr 2007) $ - * $Revision: 7502 $ - * - * 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.J2SIgnoreImport; -import javajs.api.JSONEncodable; - -/** - * a combination of Parsing and Text-related utility classes - * - * @author hansonr - * - */ - -@J2SIgnoreImport(value = { java.lang.reflect.Array.class }) -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 tokenCount = 0; - int pt = -1; - while ((pt = line.indexOf(c, pt + 1)) >= 0) - tokenCount++; - return tokenCount; - } - - 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); - } - - /** - * 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 - s = rep(s, "\"", "\\\""); - s = rep(s, "\n", "\\n"); - s = "\"" + 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, arrayGet(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, 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 - * - * var s = x.toString(); return (s.startsWith("[object") && - * s.endsWith("Array]") ? null : s); - * - */ - { - 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, "]", "%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 - */ - - public 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 = true; - ++ich; - } - if ((ch = strFormat.charAt(ich)) >= '0' && ch <= '9') { - precision = ch - '0'; - ++ich; - } - if (isExponential) - precision = -precision - (strT == null ? 1 : 0); - } - String st = strFormat.substring(ich, ich + len); - if (!st.equals(key)) { - ich = ichPercent + 1; - strLabel += '%'; - continue; - } - ich += len; - if (!Float.isNaN(floatT)) - strLabel += formatF(floatT, width, precision, alignLeft, - zeroPad); - else if (strT != null) - strLabel += formatS(strT, width, precision, alignLeft, - zeroPad); - else if (!Double.isNaN(doubleT)) - strLabel += formatD(doubleT, width, precision, 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"); -// } -} +/* $RCSfile$ + * $Author: hansonr $ + * $Date: 2007-04-26 16:57:51 -0500 (Thu, 26 Apr 2007) $ + * $Revision: 7502 $ + * + * 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.J2SIgnoreImport; +import javajs.api.JSONEncodable; + +/** + * a combination of Parsing and Text-related utility classes + * + * @author hansonr + * + */ + +@J2SIgnoreImport(value = { java.lang.reflect.Array.class }) +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 tokenCount = 0; + int pt = -1; + while ((pt = line.indexOf(c, pt + 1)) >= 0) + tokenCount++; + return tokenCount; + } + + 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); + } + + /** + * 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 + s = rep(s, "\"", "\\\""); + s = rep(s, "\n", "\\n"); + s = "\"" + 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, arrayGet(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, 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 + * + * var s = x.toString(); return (s.startsWith("[object") && + * s.endsWith("Array]") ? null : s); + * + */ + { + 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, "]", "%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 + */ + + public 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 = true; + ++ich; + } + if ((ch = strFormat.charAt(ich)) >= '0' && ch <= '9') { + precision = ch - '0'; + ++ich; + } + if (isExponential) + precision = -precision - (strT == null ? 1 : 0); + } + String st = strFormat.substring(ich, ich + len); + if (!st.equals(key)) { + ich = ichPercent + 1; + strLabel += '%'; + continue; + } + ich += len; + if (!Float.isNaN(floatT)) + strLabel += formatF(floatT, width, precision, alignLeft, + zeroPad); + else if (strT != null) + strLabel += formatS(strT, width, precision, alignLeft, + zeroPad); + else if (!Double.isNaN(doubleT)) + strLabel += formatD(doubleT, width, precision, 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"); +// } +}