3 * $Date: 2007-04-26 16:57:51 -0500 (Thu, 26 Apr 2007) $
6 * Some portions of this file have been modified by Robert Hanson hansonr.at.stolaf.edu 2012-2017
7 * for use in SwingJS via transpilation into JavaScript using Java2Script.
9 * Copyright (C) 2005 The Jmol Development Team
11 * Contact: jmol-developers@lists.sf.net
13 * This library is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU Lesser General Public
15 * License as published by the Free Software Foundation; either
16 * version 2.1 of the License, or (at your option) any later version.
18 * This library is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 * Lesser General Public License for more details.
23 * You should have received a copy of the GNU Lesser General Public
24 * License along with this library; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
30 import java.lang.reflect.Array;
32 import java.util.Map.Entry;
34 import javajs.J2SIgnoreImport;
35 import javajs.api.JSONEncodable;
38 * a combination of Parsing and Text-related utility classes
44 @J2SIgnoreImport(value = { java.lang.reflect.Array.class })
47 public static int parseInt(String str) {
48 return parseIntNext(str, new int[] {0});
51 public static int parseIntNext(String str, int[] next) {
52 int cch = str.length();
53 if (next[0] < 0 || next[0] >= cch)
54 return Integer.MIN_VALUE;
55 return parseIntChecked(str, cch, next);
58 public static int parseIntChecked(String str, int ichMax, int[] next) {
59 boolean digitSeen = false;
63 return Integer.MIN_VALUE;
65 while (ich < ichMax && isWhiteSpace(str, ich))
67 boolean negative = false;
68 if (ich < ichMax && str.charAt(ich) == 45) { //"-"
72 while (ich < ichMax && (ch = str.charAt(ich)) >= 48 && ch <= 57) {
73 value = value * 10 + (ch - 48);
77 if (!digitSeen)// || !checkTrailingText(str, ich, ichMax))
78 value = Integer.MIN_VALUE;
85 public static boolean isWhiteSpace(String str, int ich) {
87 return (ich >= 0 && ((ch = str.charAt(ich)) == ' ' || ch == '\t' || ch == '\n'));
91 * A float parser that is 30% faster than Float.parseFloat(x) and also accepts
97 * pointer; incremented
99 * @return value or Float.NaN
101 public static float parseFloatChecked(String str, int ichMax, int[] next,
103 boolean digitSeen = false;
105 if (isStrict && str.indexOf('\n') != str.lastIndexOf('\n'))
107 while (ich < ichMax && isWhiteSpace(str, ich))
109 boolean negative = false;
110 if (ich < ichMax && str.charAt(ich) == '-') {
114 // looks crazy, but if we don't do this, Google Closure Compiler will
115 // write code that Safari will misinterpret in a VERY nasty way --
116 // getting totally confused as to long integers and double values
118 // This is Safari figuring out the values of the numbers on the line (x, y, then z):
120 // ATOM 1241 CD1 LEU A 64 -2.206 36.532 31.576 1.00 60.60 C
123 // ATOM 1241 CD1 LEU A 64 -2.206 36.532 31.576 1.00 60.60 C
126 // ATOM 1241 CD1 LEU A 64 -2.206 36.532 31.576 1.00 60.60 C
130 // "e" values are just before and after the "value = -value" statement.
135 while (ich < ichMax && (ch = str.charAt(ich)) >= 48 && ch <= 57) {
136 ival = (ival * 10f) + (ch - 48)*1f;
140 boolean isDecimal = false;
142 int nzero = (ival == 0 ? -1 : 0);
145 while (++ich < ichMax && (ch = str.charAt(ich)) >= 48 && ch <= 57) {
154 if (iscale < decimalScale.length) {
155 ival2 = (ival2 * 10f) + (ch - 48)*1f;
162 // Safari breaks here intermittently converting integers to floats
166 } else if (ival2 > 0) {
167 value = ival2 * decimalScale[iscale - 1];
169 if (nzero - 2 < decimalScale.length) {
170 value *= decimalScale[nzero - 2];
172 value *= Math.pow(10, 1 - nzero);
180 boolean isExponent = false;
181 if (ich < ichMax && (ch == 69 || ch == 101 || ch == 68)) { // E e D
185 ch = str.charAt(ich);
186 if ((ch == '+') && (++ich >= ichMax))
189 int exponent = parseIntChecked(str, ichMax, next);
190 if (exponent == Integer.MIN_VALUE)
192 if (exponent > 0 && exponent <= tensScale.length)
193 value *= tensScale[exponent - 1];
194 else if (exponent < 0 && -exponent <= decimalScale.length)
195 value *= decimalScale[-exponent - 1];
196 else if (exponent != 0)
197 value *= Math.pow(10, exponent);
199 next[0] = ich; // the exponent code finds its own ichNextParse
201 // believe it or not, Safari reports the long-equivalent of the
202 // float value here, then later the float value, after no operation!
205 if (value == Float.POSITIVE_INFINITY)
206 value = Float.MAX_VALUE;
207 return (!isStrict || (!isExponent || isDecimal)
208 && checkTrailingText(str, next[0], ichMax) ? value : Float.NaN);
211 public final static float[] tensScale = { 10f, 100f, 1000f, 10000f, 100000f, 1000000f };
212 public final static float[] decimalScale = {
223 public static boolean checkTrailingText(String str, int ich, int ichMax) {
224 //number must be pure -- no additional characters other than white space or ;
226 while (ich < ichMax && (isWhitespace(ch = str.charAt(ich)) || ch == ';'))
228 return (ich == ichMax);
231 public static float[] parseFloatArray(String str) {
232 return parseFloatArrayNext(str, new int[1], null, null, null);
235 public static int parseFloatArrayInfested(String[] tokens, float[] data) {
236 int len = data.length;
237 int nTokens = tokens.length;
240 for (int i = 0; i >= 0 && i < len && n < nTokens; i++) {
242 while (Float.isNaN(f = parseFloat(tokens[n++]))
257 * @param strStart or null
258 * @param strEnd or null
259 * @return array of float values
262 public static float[] parseFloatArrayNext(String str, int[] next, float[] f,
263 String strStart, String strEnd) {
267 if (strStart != null) {
268 int p = str.indexOf(strStart, pt);
270 next[0] = p + strStart.length();
272 str = str.substring(next[0]);
273 pt = (strEnd == null ? -1 : str.indexOf(strEnd));
277 str = str.substring(0, pt);
279 String[] tokens = getTokens(str);
281 f = new float[tokens.length];
282 n = parseFloatArrayInfested(tokens, f);
286 for (int i = n; i < f.length; i++)
291 public static float parseFloatRange(String str, int ichMax, int[] next) {
292 int cch = str.length();
295 if (next[0] < 0 || next[0] >= ichMax)
297 return parseFloatChecked(str, ichMax, next, false);
300 public static float parseFloatNext(String str, int[] next) {
301 int cch = (str == null ? -1 : str.length());
302 return (next[0] < 0 || next[0] >= cch ? Float.NaN : parseFloatChecked(str, cch, next, false));
305 public static float parseFloatStrict(String str) {
306 // checks trailing characters and does not allow "1E35" to be float
307 int cch = str.length();
310 return parseFloatChecked(str, cch, new int[] {0}, true);
313 public static float parseFloat(String str) {
314 return parseFloatNext(str, new int[] {0});
317 public static int parseIntRadix(String s, int i) throws NumberFormatException {
320 * JavaScript uses parseIntRadix
324 * return Integer.parseIntRadix(s, i);
328 return Integer.parseInt(s, i);
332 public static String[] getTokens(String line) {
333 return getTokensAt(line, 0);
336 public static String parseToken(String str) {
337 return parseTokenNext(str, new int[] {0});
340 public static String parseTrimmed(String str) {
341 return parseTrimmedRange(str, 0, str.length());
344 public static String parseTrimmedAt(String str, int ichStart) {
345 return parseTrimmedRange(str, ichStart, str.length());
348 public static String parseTrimmedRange(String str, int ichStart, int ichMax) {
349 int cch = str.length();
354 return parseTrimmedChecked(str, ichStart, cch);
357 public static String[] getTokensAt(String line, int ich) {
360 int cchLine = line.length();
361 if (ich < 0 || ich > cchLine)
363 int tokenCount = countTokens(line, ich);
364 String[] tokens = new String[tokenCount];
365 int[] next = new int[1];
367 for (int i = 0; i < tokenCount; ++i)
368 tokens[i] = parseTokenChecked(line, cchLine, next);
372 public static int countChar(String line, char c) {
374 for (int i = line.lastIndexOf(c) + 1; --i >= 0;)
375 if (line.charAt(i) == c)
380 public static int countTokens(String line, int ich) {
383 int ichMax = line.length();
385 while (ich < ichMax && isWhiteSpace(line, ich))
392 } while (ich < ichMax && !isWhiteSpace(line, ich));
398 public static String parseTokenNext(String str, int[] next) {
399 int cch = str.length();
400 return (next[0] < 0 || next[0] >= cch ? null : parseTokenChecked(str, cch, next));
403 public static String parseTokenRange(String str, int ichMax, int[] next) {
404 int cch = str.length();
407 return (next[0] < 0 || next[0] >= ichMax ? null : parseTokenChecked(str, ichMax, next));
410 public static String parseTokenChecked(String str, int ichMax, int[] next) {
412 while (ich < ichMax && isWhiteSpace(str, ich))
414 int ichNonWhite = ich;
415 while (ich < ichMax && !isWhiteSpace(str, ich))
418 return (ichNonWhite == ich ? null : str.substring(ichNonWhite, ich));
421 public static String parseTrimmedChecked(String str, int ich, int ichMax) {
422 while (ich < ichMax && isWhiteSpace(str, ich))
424 int ichLast = ichMax - 1;
425 while (ichLast >= ich && isWhiteSpace(str, ichLast))
427 return (ichLast < ich ? "" : str.substring(ich, ichLast + 1));
430 public static double dVal(String s) throws NumberFormatException {
435 * throw new NumberFormatException("null");
436 * var d=parseFloat(s);
438 * throw new NumberFormatException("Not a Number : "+s);
443 return Double.valueOf(s).doubleValue();
447 public static float fVal(String s) throws NumberFormatException {
451 * return this.dVal(s);
454 return Float.parseFloat(s);
458 public static int parseIntRange(String str, int ichMax, int[] next) {
459 int cch = str.length();
462 return (next[0] < 0 || next[0] >= ichMax ? Integer.MIN_VALUE : parseIntChecked(str, ichMax, next));
466 * parses a string array for floats. Returns NaN for nonfloats.
468 * @param tokens the strings to parse
469 * @param data the array to fill
471 public static void parseFloatArrayData(String[] tokens, float[] data) {
472 parseFloatArrayDataN(tokens, data, data.length);
476 * parses a string array for floats. Returns NaN for nonfloats or missing data.
478 * @param tokens the strings to parse
479 * @param data the array to fill
480 * @param nData the number of elements
482 public static void parseFloatArrayDataN(String[] tokens, float[] data, int nData) {
483 for (int i = nData; --i >= 0;)
484 data[i] = (i >= tokens.length ? Float.NaN : parseFloat(tokens[i]));
489 * proper splitting, even for Java 1.3 -- if the text ends in the run,
490 * no new line is appended.
494 * @return String array
496 public static String[] split(String text, String run) {
497 if (text.length() == 0)
498 return new String[0];
500 int i = text.indexOf(run);
502 int runLen = run.length();
503 if (i < 0 || runLen == 0) {
504 lines = new String[1];
508 int len = text.length() - runLen;
509 for (; i >= 0 && i < len; n++)
510 i = text.indexOf(run, i + runLen);
511 lines = new String[n];
515 for (; (ipt = text.indexOf(run, i)) >= 0 && pt + 1 < n;) {
516 lines[pt++] = text.substring(i, ipt);
519 if (text.indexOf(run, len) != len)
521 lines[pt] = text.substring(i, len);
525 public final static float FLOAT_MIN_SAFE = 2E-45f;
526 // Float.MIN_VALUE (1.45E-45) is not reliable with JavaScript because of the float/double difference there
528 /// general static string-parsing class ///
530 // next[0] tracks the pointer within the string so these can all be static.
531 // but the methods parseFloat, parseInt, parseToken, parseTrimmed, and getTokens do not require this.
533 // public static String concatTokens(String[] tokens, int iFirst, int iEnd) {
536 // for (int i = iFirst; i < iEnd; i++) {
537 // if (i < tokens.length) {
538 // str += sep + tokens[i];
545 public static String getQuotedStringAt(String line, int ipt0) {
546 int[] next = new int[] { ipt0 };
547 return getQuotedStringNext(line, next);
553 * @param next passes [current pointer]
554 * @return quoted string -- does NOT unescape characters
556 public static String getQuotedStringNext(String line, int[] next) {
558 if (i < 0 || (i = line.indexOf("\"", i)) < 0)
561 int len = line.length();
562 while (++i < len && line.charAt(i) != '"')
563 if (line.charAt(i) == '\\')
566 return line.substring(pt, i);
570 * CSV format -- escaped quote is "" WITHIN "..."
574 * @param next int[2] filled with [ptrQuote1, ptrAfterQuote2]
575 * next[1] will be -1 if unmatched quotes are found (continuation on next line)
576 * @return unescaped string or null
578 public static String getCSVString(String line, int[] next) {
580 if (i < 0 || (i = line.indexOf("\"", i)) < 0)
582 int pt = next[0] = i;
583 int len = line.length();
584 boolean escaped = false;
585 boolean haveEscape = false;
587 && (line.charAt(i) != '"' || (escaped = (i + 1 < len && line.charAt(i + 1) == '"'))))
595 return null; // unmatched
598 String s = line.substring(pt + 1, i);
599 return (haveEscape ? rep(rep(s, "\"\"", "\0"), "\0","\"") : s);
602 public static boolean isOneOf(String key, String semiList) {
603 if (semiList.length() == 0)
605 if (semiList.charAt(0) != ';')
606 semiList = ";" + semiList + ";";
607 return key.indexOf(";") < 0 && semiList.indexOf(';' + key + ';') >= 0;
610 public static String getQuotedAttribute(String info, String name) {
611 int i = info.indexOf(name + "=");
612 return (i < 0 ? null : getQuotedStringAt(info, i));
615 public static float approx(float f, float n) {
616 return Math.round (f * n) / n;
620 * Does a clean ITERATIVE replace of strFrom in str with strTo.
621 * Thus, rep("Testttt", "tt","t") becomes "Test".
626 * @return replaced string
628 public static String rep(String str, String strFrom, String strTo) {
629 if (str == null || strFrom.length() == 0 || str.indexOf(strFrom) < 0)
631 boolean isOnce = (strTo.indexOf(strFrom) >= 0);
633 str = str.replace(strFrom, strTo);
634 } while (!isOnce && str.indexOf(strFrom) >= 0);
638 public static String formatF(float value, int width, int precision,
639 boolean alignLeft, boolean zeroPad) {
640 return formatS(DF.formatDecimal(value, precision), width, 0, alignLeft, zeroPad);
650 * @param allowOverflow IGNORED
651 * @return formatted string
653 public static String formatD(double value, int width, int precision,
654 boolean alignLeft, boolean zeroPad, boolean allowOverflow) {
655 return formatS(DF.formatDecimal((float)value, -1 - precision), width, 0, alignLeft, zeroPad);
661 * @param width number of columns
662 * @param precision precision > 0 ==> precision = number of characters max from left
663 * precision < 0 ==> -1 - precision = number of char. max from right
665 * @param zeroPad generally for numbers turned strings
666 * @return formatted string
668 public static String formatS(String value, int width, int precision,
669 boolean alignLeft, boolean zeroPad) {
672 int len = value.length();
673 if (precision != Integer.MAX_VALUE && precision > 0
675 value = value.substring(0, precision);
676 else if (precision < 0 && len + precision >= 0)
677 value = value.substring(len + precision + 1);
679 int padLength = width - value.length();
682 boolean isNeg = (zeroPad && !alignLeft && value.charAt(0) == '-');
683 char padChar = (zeroPad ? '0' : ' ');
684 char padChar0 = (isNeg ? '-' : padChar);
689 sb.appendC(padChar0);
690 for (int i = padLength; --i > 0;)
691 // this is correct, not >= 0
694 sb.append(isNeg ? padChar + value.substring(1) : value);
695 return sb.toString();
699 * Does a clean replace of any of the characters in str with chrTo
700 * If strTo contains strFrom, then only a single pass is done.
701 * Otherwise, multiple passes are made until no more replacements can be made.
706 * @return replaced string
708 public static String replaceWithCharacter(String str, String strFrom,
712 for (int i = strFrom.length(); --i >= 0;)
713 str = str.replace(strFrom.charAt(i), chTo);
718 * Does a clean replace of any of the characters in str with strTo
719 * If strTo contains strFrom, then only a single pass is done.
720 * Otherwise, multiple passes are made until no more replacements can be made.
725 * @return replaced string
727 public static String replaceAllCharacters(String str, String strFrom,
729 for (int i = strFrom.length(); --i >= 0;) {
730 String chFrom = strFrom.substring(i, i + 1);
731 str = rep(str, chFrom, strTo);
736 public static String trim(String str, String chars) {
737 if (str == null || str.length() == 0)
739 if (chars.length() == 0)
741 int len = str.length();
743 while (k < len && chars.indexOf(str.charAt(k)) >= 0)
745 int m = str.length() - 1;
746 while (m > k && chars.indexOf(str.charAt(m)) >= 0)
748 return str.substring(k, m + 1);
751 public static String trimQuotes(String value) {
752 return (value != null && value.length() > 1 && value.startsWith("\"")
753 && value.endsWith("\"") ? value.substring(1, value.length() - 1)
757 public static boolean isNonStringPrimitive(Object info) {
758 // note that we don't use Double, Float, or Integer here
759 // because in JavaScript those would be false for unwrapped primitives
760 // coming from equivalent of Array.get()
761 // Strings will need their own escaped processing
763 return info instanceof Number || info instanceof Boolean;
766 private static Object arrayGet(Object info, int i) {
769 * Note that info will be a primitive in JavaScript
770 * but a wrapped primitive in Java.
777 return Array.get(info, i);
781 @SuppressWarnings("unchecked")
782 public static String toJSON(String infoType, Object info) {
784 return packageJSON(infoType, null);
785 if (isNonStringPrimitive(info))
786 return packageJSON(infoType, info.toString());
790 if (info instanceof String) {
795 * if (typeof s == "undefined") s = "null"
800 if (s.indexOf("{\"") != 0) {
801 //don't doubly fix JSON strings when retrieving status
802 // what about \1 \2 \3 etc.?
807 if (info instanceof JSONEncodable) {
808 // includes javajs.util.BS, org.jmol.script.SV
809 if ((s = ((JSONEncodable) info).toJSON()) == null)
810 s = "null"; // perhaps a list has a null value (group3List, for example)
814 if (info instanceof Map) {
817 for (String key : ((Map<String, ?>) info).keySet()) {
818 sb.append(sep).append(
819 packageJSON(key, toJSON(null, ((Map<?, ?>) info).get(key))));
825 if (info instanceof Lst) {
827 int n = ((Lst<?>) info).size();
828 for (int i = 0; i < n; i++) {
831 sb.append(toJSON(null, ((Lst<?>) info).get(i)));
836 if (info instanceof M34) {
838 int len = (info instanceof M4 ? 4 : 3);
839 float[] x = new float[len];
842 for (int i = 0; i < len; i++) {
846 sb.append(toJSON(null, x));
851 s = nonArrayString(info);
854 int n = AU.getLength(info);
855 for (int i = 0; i < n; i++) {
858 sb.append(toJSON(null, arrayGet(info, i)));
863 info = info.toString();
865 return packageJSON(infoType, (s == null ? sb.toString() : s));
869 * Checks to see if an object is an array (including typed arrays), and if it is, returns null;
870 * otherwise it returns the string equivalent of that object.
873 * @return String or null
875 public static String nonArrayString(Object x) {
879 * return (x.constructor == Array || x.__BYTESIZE ? null : x.toString());
886 } catch (Exception e) {
892 public static String byteArrayToJSON(byte[] data) {
896 for (int i = 0; i < n; i++) {
899 sb.appendI(data[i] & 0xFF);
902 return sb.toString();
905 public static String packageJSON(String infoType, String info) {
906 return (infoType == null ? info : "\"" + infoType + "\": " + info);
909 public static String escapeUrl(String url) {
910 url = rep(url, "\n", "");
911 url = rep(url, "%", "%25");
912 url = rep(url, "#", "%23");
913 url = rep(url, "[", "%5B");
914 url = rep(url, "\\", "%5C");
915 url = rep(url, "]", "%5D");
916 url = rep(url, " ", "%20");
920 private final static String escapable = "\\\\\tt\rr\nn\"\"";
922 public static String esc(String str) {
923 if (str == null || str.length() == 0)
925 boolean haveEscape = false;
927 for (; i < escapable.length(); i += 2)
928 if (str.indexOf(escapable.charAt(i)) >= 0) {
933 while (i < escapable.length()) {
935 char ch = escapable.charAt(i++);
936 char ch2 = escapable.charAt(i++);
939 while ((pt = str.indexOf(ch, pt + 1)) >= 0) {
940 sb.append(str.substring(pt0, pt)).appendC('\\').appendC(ch2);
943 sb.append(str.substring(pt0, str.length()));
946 return "\"" + escUnicode(str) + "\"";
949 public static String escUnicode(String str) {
950 for (int i = str.length(); --i >= 0;)
951 if (str.charAt(i) > 0x7F) {
952 String s = "0000" + Integer.toHexString(str.charAt(i));
953 str = str.substring(0, i) + "\\u" + s.substring(s.length() - 4)
954 + str.substring(i + 1);
960 * ensures that a float turned to string has a decimal point
963 * @return string version of float
965 public static String escF(float f) {
970 * if (sf.indexOf(".") < 0 && sf.indexOf("e") < 0)
977 public static String join(String[] s, char c, int i0) {
982 for (int i = i0; i < s.length; i++)
983 sb.appendC(c).append(s[i]);
984 return sb.toString();
988 * a LIKE "x" a is a string and equals x
990 * a LIKE "*x" a is a string and ends with x
992 * a LIKE "x*" a is a string and starts with x
994 * a LIKE "*x*" a is a string and contains x
1000 public static boolean isLike(String a, String b) {
1001 boolean areEqual = a.equals(b);
1004 boolean isStart = b.startsWith("*");
1005 boolean isEnd = b.endsWith("*");
1006 return (!isStart && !isEnd) ? areEqual
1007 : isStart && isEnd ? b.length() == 1 || a.contains(b.substring(1, b.length() - 1))
1008 : isStart ? a.endsWith(b.substring(1))
1009 : a.startsWith(b.substring(0, b.length() - 1));
1012 public static Object getMapValueNoCase(Map<String, ?> h, String key) {
1013 if ("this".equals(key))
1015 Object val = h.get(key);
1017 for (Entry<String, ?> e : h.entrySet())
1018 if (e.getKey().equalsIgnoreCase(key))
1019 return e.getValue();
1023 public static String clean(String s) {
1024 return rep(replaceAllCharacters(s, " \t\n\r", " "), " ", " ").trim();
1029 * fdup duplicates p or q formats for formatCheck
1030 * and the format() function.
1035 * @return %3.5q%3.5q%3.5q%3.5q or %3.5p%3.5p%3.5p
1037 public static String fdup(String f, int pt, int n) {
1040 for (int i = pt; --i >= 1; ) {
1041 if (isDigit(ch = f.charAt(i)))
1049 if (i != 1 && f.charAt(i - 1) != '.')
1056 String s = f.substring(0, pt + 1);
1058 for (int i = 0; i < n; i++)
1060 sb.append(f.substring(pt + 1));
1061 return sb.toString();
1065 * generic string formatter based on formatLabel in Atom
1068 * @param strFormat .... %width.precisionKEY....
1069 * @param key any string to match
1070 * @param strT replacement string or null
1071 * @param floatT replacement float or Float.NaN
1072 * @param doubleT replacement double or Double.NaN -- for exponential
1073 * @param doOne mimic sprintf
1074 * @return formatted string
1077 private static String formatString(String strFormat, String key, String strT,
1078 float floatT, double doubleT, boolean doOne) {
1079 if (strFormat == null)
1081 if ("".equals(strFormat))
1083 int len = key.length();
1084 if (strFormat.indexOf("%") < 0 || len == 0 || strFormat.indexOf(key) < 0)
1087 String strLabel = "";
1088 int ich, ichPercent, ichKey;
1089 for (ich = 0; (ichPercent = strFormat.indexOf('%', ich)) >= 0
1090 && (ichKey = strFormat.indexOf(key, ichPercent + 1)) >= 0;) {
1091 if (ich != ichPercent)
1092 strLabel += strFormat.substring(ich, ichPercent);
1093 ich = ichPercent + 1;
1094 if (ichKey > ichPercent + 6) {
1099 boolean alignLeft = false;
1100 if (strFormat.charAt(ich) == '-') {
1104 boolean zeroPad = false;
1105 if (strFormat.charAt(ich) == '0') {
1111 while ((ch = strFormat.charAt(ich)) >= '0' && (ch <= '9')) {
1112 width = (10 * width) + (ch - '0');
1115 int precision = Integer.MAX_VALUE;
1116 boolean isExponential = false;
1117 if (strFormat.charAt(ich) == '.') {
1119 if ((ch = strFormat.charAt(ich)) == '-') {
1120 isExponential = (strT == null);
1123 if ((ch = strFormat.charAt(ich)) >= '0' && ch <= '9') {
1124 precision = ch - '0';
1128 precision = -precision;
1130 String st = strFormat.substring(ich, ich + len);
1131 if (!st.equals(key)) {
1132 ich = ichPercent + 1;
1137 if (!Float.isNaN(floatT)) // 'f'
1138 strLabel += formatF(floatT, width, precision, alignLeft,
1140 else if (strT != null) // 'd' 'i' or 's'
1141 strLabel += formatS(strT, width, precision, alignLeft,
1143 else if (!Double.isNaN(doubleT)) // 'e'
1144 strLabel += formatD(doubleT, width, precision - 1, alignLeft,
1148 } catch (IndexOutOfBoundsException ioobe) {
1153 strLabel += strFormat.substring(ich);
1154 //if (strLabel.length() == 0)
1159 public static String formatStringS(String strFormat, String key, String strT) {
1160 return formatString(strFormat, key, strT, Float.NaN, Double.NaN, false);
1163 public static String formatStringF(String strFormat, String key, float floatT) {
1164 return formatString(strFormat, key, null, floatT, Double.NaN, false);
1167 public static String formatStringI(String strFormat, String key, int intT) {
1168 return formatString(strFormat, key, "" + intT, Float.NaN, Double.NaN, false);
1172 * sprintf emulation uses (almost) c++ standard string formats
1174 * 's' string 'i' or 'd' integer, 'e' double, 'f' float, 'p' point3f 'q'
1175 * quaternion/plane/axisangle with added "i" (equal to the insipid "d" --
1180 * a listing of what sort of data will be found in Object[] values, in
1181 * order: s string, f float, i integer, d double, p point3f, q
1182 * quaternion/point4f, S String[], F float[], I int[], and D double[]
1184 * Object[] containing above types
1185 * @return formatted string
1187 public static String sprintf(String strFormat, String list, Object[] values) {
1190 int n = list.length();
1191 if (n == values.length)
1193 for (int o = 0; o < n; o++) {
1194 if (values[o] == null)
1196 switch (list.charAt(o)) {
1198 strFormat = formatString(strFormat, "s", (String) values[o],
1199 Float.NaN, Double.NaN, true);
1202 strFormat = formatString(strFormat, "f", null, ((Float) values[o])
1203 .floatValue(), Double.NaN, true);
1206 strFormat = formatString(strFormat, "d", "" + values[o], Float.NaN,
1208 strFormat = formatString(strFormat, "i", "" + values[o], Float.NaN,
1212 strFormat = formatString(strFormat, "e", null, Float.NaN,
1213 ((Double) values[o]).doubleValue(), true);
1216 T3 pVal = (T3) values[o];
1217 strFormat = formatString(strFormat, "p", null, pVal.x, Double.NaN,
1219 strFormat = formatString(strFormat, "p", null, pVal.y, Double.NaN,
1221 strFormat = formatString(strFormat, "p", null, pVal.z, Double.NaN,
1225 T4 qVal = (T4) values[o];
1226 strFormat = formatString(strFormat, "q", null, qVal.x, Double.NaN,
1228 strFormat = formatString(strFormat, "q", null, qVal.y, Double.NaN,
1230 strFormat = formatString(strFormat, "q", null, qVal.z, Double.NaN,
1232 strFormat = formatString(strFormat, "q", null, qVal.w, Double.NaN,
1236 String[] sVal = (String[]) values[o];
1237 for (int i = 0; i < sVal.length; i++)
1238 strFormat = formatString(strFormat, "s", sVal[i], Float.NaN,
1242 float[] fVal = (float[]) values[o];
1243 for (int i = 0; i < fVal.length; i++)
1244 strFormat = formatString(strFormat, "f", null, fVal[i],
1248 int[] iVal = (int[]) values[o];
1249 for (int i = 0; i < iVal.length; i++)
1250 strFormat = formatString(strFormat, "d", "" + iVal[i], Float.NaN,
1252 for (int i = 0; i < iVal.length; i++)
1253 strFormat = formatString(strFormat, "i", "" + iVal[i], Float.NaN,
1257 double[] dVal = (double[]) values[o];
1258 for (int i = 0; i < dVal.length; i++)
1259 strFormat = formatString(strFormat, "e", null, Float.NaN,
1264 return rep(strFormat, "%%", "%");
1265 } catch (Exception e) {
1268 System.out.println("TextFormat.sprintf error " + list + " " + strFormat);
1269 return rep(strFormat, "%", "?");
1274 * formatCheck checks p and q formats and duplicates if necessary
1275 * "%10.5p xxxx" ==> "%10.5p%10.5p%10.5p xxxx"
1278 * @return f or dupicated format
1280 public static String formatCheck(String strFormat) {
1281 if (strFormat == null || strFormat.indexOf('p') < 0 && strFormat.indexOf('q') < 0)
1283 strFormat = rep(strFormat, "%%", "\1");
1284 strFormat = rep(strFormat, "%p", "%6.2p");
1285 strFormat = rep(strFormat, "%q", "%6.2q");
1286 String[] format = split(strFormat, "%");
1288 sb.append(format[0]);
1289 for (int i = 1; i < format.length; i++) {
1290 String f = "%" + format[i];
1292 if (f.length() >= 3) {
1293 if ((pt = f.indexOf('p')) >= 0)
1295 if ((pt = f.indexOf('q')) >= 0)
1300 return sb.toString().replace('\1', '%');
1303 public static void leftJustify(SB s, String s1, String s2) {
1305 int n = s1.length() - s2.length();
1307 s.append(s1.substring(0, n));
1310 public static void rightJustify(SB s, String s1, String s2) {
1311 int n = s1.length() - s2.length();
1313 s.append(s1.substring(0, n));
1317 public static String safeTruncate(float f, int n) {
1318 if (f > -0.001 && f < 0.001)
1320 return (f + " ").substring(0,n);
1323 public static boolean isWild(String s) {
1324 return s != null && (s.indexOf("*") >= 0 || s.indexOf("?") >= 0);
1328 * A general non-regex (for performance) text matcher that utilizes ? and *.
1330 * ??? means "at most three" characters if at beginning or end;
1331 * "exactly three" otherwise
1332 * \1 in search is a stand-in for actual ?
1335 * the string to search
1339 * @param allowInitialStar
1340 * @return true if found
1342 public static boolean isMatch(String search, String match, boolean checkStar,
1343 boolean allowInitialStar) {
1344 // search == match --> true
1345 if (search.equals(match))
1347 int mLen = match.length();
1348 // match == "" --> false
1351 boolean isStar0 = (checkStar && allowInitialStar ? match.charAt(0) == '*'
1353 // match == "*" --> true
1354 if (mLen == 1 && isStar0)
1356 boolean isStar1 = (checkStar && match.endsWith("*"));
1357 boolean haveQ = (match.indexOf('?') >= 0);
1358 // match == "**" --> true
1359 // match == "*xxx*" --> search contains "xxx"
1360 // match == "*xxx" --> search ends with "xxx"
1361 // match == "xxx*" --> search starts with "xxx"
1364 return (isStar1 ? (mLen < 3 || search.indexOf(match.substring(1,
1365 mLen - 1)) >= 0) : search.endsWith(match.substring(1)));
1367 return search.startsWith(match.substring(0, mLen - 1));
1369 int sLen = search.length();
1370 // pad match with "?" -- same as *
1371 String qqqq = "????";
1379 match = qqqq + match.substring(1);
1383 match = match.substring(0, mLen - 1) + qqqq;
1387 // length of match < length of search --> false
1391 // -- each ? matches ONE character if not at end
1392 // -- extra ? at end ignored
1394 // (allowInitialStar == true)
1395 // -- extra ? at beginning reduced to match length
1398 while (mLen > sLen) {
1399 if (allowInitialStar && match.charAt(ich) == '?') {
1401 } else if (match.charAt(ich + mLen - 1) != '?') {
1407 // both are effectively same length now.
1408 // \1 is stand-in for "?"
1410 for (int i = sLen; --i >= 0;) {
1411 char chm = match.charAt(ich + i);
1414 char chs = search.charAt(i);
1415 if (chm != chs && (chm != '\1' || chs != '?'))
1421 public static String replaceQuotedStrings(String s, Lst<String> list,
1422 Lst<String> newList) {
1423 int n = list.size();
1424 for (int i = 0; i < n; i++) {
1425 String name = list.get(i);
1426 String newName = newList.get(i);
1427 if (!newName.equals(name))
1428 s = rep(s, "\"" + name + "\"", "\"" + newName
1434 public static String replaceStrings(String s, Lst<String> list,
1435 Lst<String> newList) {
1436 int n = list.size();
1437 for (int i = 0; i < n; i++) {
1438 String name = list.get(i);
1439 String newName = newList.get(i);
1440 if (!newName.equals(name))
1441 s = rep(s, name, newName);
1446 public static boolean isDigit(char ch) {
1447 // just way simpler code than Character.isDigit(ch);
1449 return (48 <= c && c <= 57);
1452 public static boolean isUpperCase(char ch) {
1454 return (65 <= c && c <= 90);
1457 public static boolean isLowerCase(char ch) {
1459 return (97 <= c && c <= 122);
1462 public static boolean isLetter(char ch) {
1463 // just way simpler code than Character.isLetter(ch);
1465 return (65 <= c && c <= 90 || 97 <= c && c <= 122);
1468 public static boolean isLetterOrDigit(char ch) {
1469 // just way simpler code than Character.isLetterOrDigit(ch);
1471 return (65 <= c && c <= 90 || 97 <= c && c <= 122 || 48 <= c && c <= 57);
1474 public static boolean isWhitespace(char ch) {
1476 return (c >= 0x1c && c <= 0x20 || c >= 0x9 && c <= 0xd);
1479 public static final float FRACTIONAL_PRECISION = 100000f;
1480 public static final float CARTESIAN_PRECISION = 10000f;
1482 public static void fixPtFloats(T3 pt, float f) {
1483 //this will equate float and double as long as -256 <= x <= 256
1484 pt.x = Math.round(pt.x * f) / f;
1485 pt.y = Math.round(pt.y * f) / f;
1486 pt.z = Math.round(pt.z * f) / f;
1489 public static double fixDouble(double d, double f) {
1490 return Math.round(d * f) / f;
1494 * parse a float or "float/float"
1498 public static float parseFloatFraction(String s) {
1499 int pt = s.indexOf("/");
1500 return (pt < 0 ? parseFloat(s) : parseFloat(s.substring(0, pt))
1501 / parseFloat(s.substring(pt + 1)));
1506 // double d = 790.8999998888;
1507 // float x = 790.8999998888f;
1508 // for (int i = 0; i < 50; i++) {
1509 // System.out.println(x + " " + d);
1510 // System.out.println(Math.round(x * 100000) / 100000f);
1511 // System.out.println(Math.round(d * 100000) / 100000.);
1512 // System.out.println(Math.round(x * 10000) / 10000f);
1513 // System.out.println(Math.round(d * 10000) / 10000.);
1517 // System.out.println(100.123456789f);
1523 // t = System.currentTimeMillis();
1524 // for (int i = 0; i < 10000000; i++) {
1525 // boolean b = PT.isUpperCase(c);
1527 // System.out.println(System.currentTimeMillis() - t);
1529 // t = System.currentTimeMillis();
1530 // for (int i = 0; i < 10000000; i++) {
1531 // boolean b = Character.isUpperCase(c);
1533 // System.out.println(System.currentTimeMillis() - t);
1535 // t = System.currentTimeMillis();
1536 // for (int i = 0; i < 10000000; i++) {
1537 // boolean b = PT.isUpperCase(c);
1539 // System.out.println(System.currentTimeMillis() - t);
1541 // System.out.println("PT test");