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.api.JSONEncodable;
37 * a combination of Parsing and Text-related utility classes
45 public static int parseInt(String str) {
46 return parseIntNext(str, new int[] {0});
49 public static int parseIntNext(String str, int[] next) {
50 int cch = str.length();
51 if (next[0] < 0 || next[0] >= cch)
52 return Integer.MIN_VALUE;
53 return parseIntChecked(str, cch, next);
56 public static int parseIntChecked(String str, int ichMax, int[] next) {
57 boolean digitSeen = false;
61 return Integer.MIN_VALUE;
63 while (ich < ichMax && isWhiteSpace(str, ich))
65 boolean negative = false;
66 if (ich < ichMax && str.charAt(ich) == 45) { //"-"
70 while (ich < ichMax && (ch = str.charAt(ich)) >= 48 && ch <= 57) {
71 value = value * 10 + (ch - 48);
75 if (!digitSeen)// || !checkTrailingText(str, ich, ichMax))
76 value = Integer.MIN_VALUE;
83 public static boolean isWhiteSpace(String str, int ich) {
85 return (ich >= 0 && ((ch = str.charAt(ich)) == ' ' || ch == '\t' || ch == '\n'));
89 * A float parser that is 30% faster than Float.parseFloat(x) and also accepts
95 * pointer; incremented
97 * @return value or Float.NaN
99 public static float parseFloatChecked(String str, int ichMax, int[] next,
101 boolean digitSeen = false;
103 if (isStrict && str.indexOf('\n') != str.lastIndexOf('\n'))
105 while (ich < ichMax && isWhiteSpace(str, ich))
107 boolean negative = false;
108 if (ich < ichMax && str.charAt(ich) == '-') {
112 // looks crazy, but if we don't do this, Google Closure Compiler will
113 // write code that Safari will misinterpret in a VERY nasty way --
114 // getting totally confused as to long integers and double values
116 // This is Safari figuring out the values of the numbers on the line (x, y, then z):
118 // ATOM 1241 CD1 LEU A 64 -2.206 36.532 31.576 1.00 60.60 C
121 // ATOM 1241 CD1 LEU A 64 -2.206 36.532 31.576 1.00 60.60 C
124 // ATOM 1241 CD1 LEU A 64 -2.206 36.532 31.576 1.00 60.60 C
128 // "e" values are just before and after the "value = -value" statement.
133 while (ich < ichMax && (ch = str.charAt(ich)) >= 48 && ch <= 57) {
134 ival = (ival * 10f) + (ch - 48)*1f;
138 boolean isDecimal = false;
140 int nzero = (ival == 0 ? -1 : 0);
143 while (++ich < ichMax && (ch = str.charAt(ich)) >= 48 && ch <= 57) {
152 if (iscale < decimalScale.length) {
153 ival2 = (ival2 * 10f) + (ch - 48)*1f;
160 // Safari breaks here intermittently converting integers to floats
164 } else if (ival2 > 0) {
165 value = ival2 * decimalScale[iscale - 1];
167 if (nzero - 2 < decimalScale.length) {
168 value *= decimalScale[nzero - 2];
170 value *= Math.pow(10, 1 - nzero);
178 boolean isExponent = false;
179 if (ich < ichMax && (ch == 69 || ch == 101 || ch == 68)) { // E e D
183 ch = str.charAt(ich);
184 if ((ch == '+') && (++ich >= ichMax))
187 int exponent = parseIntChecked(str, ichMax, next);
188 if (exponent == Integer.MIN_VALUE)
190 if (exponent > 0 && exponent <= tensScale.length)
191 value *= tensScale[exponent - 1];
192 else if (exponent < 0 && -exponent <= decimalScale.length)
193 value *= decimalScale[-exponent - 1];
194 else if (exponent != 0)
195 value *= Math.pow(10, exponent);
197 next[0] = ich; // the exponent code finds its own ichNextParse
199 // believe it or not, Safari reports the long-equivalent of the
200 // float value here, then later the float value, after no operation!
203 if (value == Float.POSITIVE_INFINITY)
204 value = Float.MAX_VALUE;
205 return (!isStrict || (!isExponent || isDecimal)
206 && checkTrailingText(str, next[0], ichMax) ? value : Float.NaN);
209 public final static float[] tensScale = { 10f, 100f, 1000f, 10000f, 100000f, 1000000f };
210 public final static float[] decimalScale = {
221 public static boolean checkTrailingText(String str, int ich, int ichMax) {
222 //number must be pure -- no additional characters other than white space or ;
224 while (ich < ichMax && (isWhitespace(ch = str.charAt(ich)) || ch == ';'))
226 return (ich == ichMax);
229 public static float[] parseFloatArray(String str) {
230 return parseFloatArrayNext(str, new int[1], null, null, null);
233 public static int parseFloatArrayInfested(String[] tokens, float[] data) {
234 int len = data.length;
235 int nTokens = tokens.length;
238 for (int i = 0; i >= 0 && i < len && n < nTokens; i++) {
240 while (Float.isNaN(f = parseFloat(tokens[n++]))
255 * @param strStart or null
256 * @param strEnd or null
257 * @return array of float values
260 public static float[] parseFloatArrayNext(String str, int[] next, float[] f,
261 String strStart, String strEnd) {
265 if (strStart != null) {
266 int p = str.indexOf(strStart, pt);
268 next[0] = p + strStart.length();
270 str = str.substring(next[0]);
271 pt = (strEnd == null ? -1 : str.indexOf(strEnd));
275 str = str.substring(0, pt);
277 String[] tokens = getTokens(str);
279 f = new float[tokens.length];
280 n = parseFloatArrayInfested(tokens, f);
284 for (int i = n; i < f.length; i++)
289 public static float parseFloatRange(String str, int ichMax, int[] next) {
290 int cch = str.length();
293 if (next[0] < 0 || next[0] >= ichMax)
295 return parseFloatChecked(str, ichMax, next, false);
298 public static float parseFloatNext(String str, int[] next) {
299 int cch = (str == null ? -1 : str.length());
300 return (next[0] < 0 || next[0] >= cch ? Float.NaN : parseFloatChecked(str, cch, next, false));
303 public static float parseFloatStrict(String str) {
304 // checks trailing characters and does not allow "1E35" to be float
305 int cch = str.length();
308 return parseFloatChecked(str, cch, new int[] {0}, true);
311 public static float parseFloat(String str) {
312 return parseFloatNext(str, new int[] {0});
315 public static int parseIntRadix(String s, int i) throws NumberFormatException {
318 // * JavaScript uses parseIntRadix
322 // * return Integer.parseIntRadix(s, i);
326 return Integer.parseInt(s, i);
330 public static String[] getTokens(String line) {
331 return getTokensAt(line, 0);
334 public static String parseToken(String str) {
335 return parseTokenNext(str, new int[] {0});
338 public static String parseTrimmed(String str) {
339 return parseTrimmedRange(str, 0, str.length());
342 public static String parseTrimmedAt(String str, int ichStart) {
343 return parseTrimmedRange(str, ichStart, str.length());
346 public static String parseTrimmedRange(String str, int ichStart, int ichMax) {
347 int cch = str.length();
352 return parseTrimmedChecked(str, ichStart, cch);
355 public static String[] getTokensAt(String line, int ich) {
358 int cchLine = line.length();
359 if (ich < 0 || ich > cchLine)
361 int tokenCount = countTokens(line, ich);
362 String[] tokens = new String[tokenCount];
363 int[] next = new int[1];
365 for (int i = 0; i < tokenCount; ++i)
366 tokens[i] = parseTokenChecked(line, cchLine, next);
370 public static int countChar(String line, char c) {
372 for (int i = line.lastIndexOf(c) + 1; --i >= 0;)
373 if (line.charAt(i) == c)
378 public static int countTokens(String line, int ich) {
381 int ichMax = line.length();
383 while (ich < ichMax && isWhiteSpace(line, ich))
390 } while (ich < ichMax && !isWhiteSpace(line, ich));
396 public static String parseTokenNext(String str, int[] next) {
397 int cch = str.length();
398 return (next[0] < 0 || next[0] >= cch ? null : parseTokenChecked(str, cch, next));
401 public static String parseTokenRange(String str, int ichMax, int[] next) {
402 int cch = str.length();
405 return (next[0] < 0 || next[0] >= ichMax ? null : parseTokenChecked(str, ichMax, next));
408 public static String parseTokenChecked(String str, int ichMax, int[] next) {
410 while (ich < ichMax && isWhiteSpace(str, ich))
412 int ichNonWhite = ich;
413 while (ich < ichMax && !isWhiteSpace(str, ich))
416 return (ichNonWhite == ich ? null : str.substring(ichNonWhite, ich));
419 public static String parseTrimmedChecked(String str, int ich, int ichMax) {
420 while (ich < ichMax && isWhiteSpace(str, ich))
422 int ichLast = ichMax - 1;
423 while (ichLast >= ich && isWhiteSpace(str, ichLast))
425 return (ichLast < ich ? "" : str.substring(ich, ichLast + 1));
428 // public static double dVal(String s) throws NumberFormatException {
433 // * throw new NumberFormatException("null");
434 // * var d=parseFloat(s);
436 // * throw new NumberFormatException("Not a Number : "+s);
441 // return Double.valueOf(s).doubleValue();
445 // public static float fVal(String s) throws NumberFormatException {
449 // * return this.dVal(s);
453 // return Float.parseFloat(s);
457 public static int parseIntRange(String str, int ichMax, int[] next) {
458 int cch = str.length();
461 return (next[0] < 0 || next[0] >= ichMax ? Integer.MIN_VALUE : parseIntChecked(str, ichMax, next));
465 * parses a string array for floats. Returns NaN for nonfloats.
467 * @param tokens the strings to parse
468 * @param data the array to fill
470 public static void parseFloatArrayData(String[] tokens, float[] data) {
471 parseFloatArrayDataN(tokens, data, data.length);
475 * parses a string array for floats. Returns NaN for nonfloats or missing data.
477 * @param tokens the strings to parse
478 * @param data the array to fill
479 * @param nData the number of elements
481 public static void parseFloatArrayDataN(String[] tokens, float[] data, int nData) {
482 for (int i = nData; --i >= 0;)
483 data[i] = (i >= tokens.length ? Float.NaN : parseFloat(tokens[i]));
488 * proper splitting, even for Java 1.3 -- if the text ends in the run,
489 * no new line is appended.
493 * @return String array
495 public static String[] split(String text, String run) {
496 if (text.length() == 0)
497 return new String[0];
499 int i = text.indexOf(run);
501 int runLen = run.length();
502 if (i < 0 || runLen == 0) {
503 lines = new String[1];
507 int len = text.length() - runLen;
508 for (; i >= 0 && i < len; n++)
509 i = text.indexOf(run, i + runLen);
510 lines = new String[n];
514 for (; (ipt = text.indexOf(run, i)) >= 0 && pt + 1 < n;) {
515 lines[pt++] = text.substring(i, ipt);
518 if (text.indexOf(run, len) != len)
520 lines[pt] = text.substring(i, len);
524 public final static float FLOAT_MIN_SAFE = 2E-45f;
525 // Float.MIN_VALUE (1.45E-45) is not reliable with JavaScript because of the float/double difference there
527 /// general static string-parsing class ///
529 // next[0] tracks the pointer within the string so these can all be static.
530 // but the methods parseFloat, parseInt, parseToken, parseTrimmed, and getTokens do not require this.
532 // public static String concatTokens(String[] tokens, int iFirst, int iEnd) {
535 // for (int i = iFirst; i < iEnd; i++) {
536 // if (i < tokens.length) {
537 // str += sep + tokens[i];
544 public static String getQuotedStringAt(String line, int ipt0) {
545 int[] next = new int[] { ipt0 };
546 return getQuotedStringNext(line, next);
552 * @param next passes [current pointer]
553 * @return quoted string -- does NOT unescape characters
555 public static String getQuotedStringNext(String line, int[] next) {
557 if (i < 0 || (i = line.indexOf("\"", i)) < 0)
560 int len = line.length();
561 while (++i < len && line.charAt(i) != '"')
562 if (line.charAt(i) == '\\')
565 return line.substring(pt, i);
569 * single- or double-quoted string or up to the first space -- like HTML5
576 public static String getQuotedOrUnquotedAttribute(String line, String key) {
577 if (line == null || key == null)
579 int pt = line.toLowerCase().indexOf(key.toLowerCase() + "=");
580 if (pt < 0 || (pt = pt + key.length() + 1) >= line.length())
582 char c = line.charAt(pt);
592 int pt1 = line.indexOf(c, pt);
593 return (pt1 < 0 ? null : line.substring(pt, pt1));
597 * CSV format -- escaped quote is "" WITHIN "..."
601 * @param next int[2] filled with [ptrQuote1, ptrAfterQuote2]
602 * next[1] will be -1 if unmatched quotes are found (continuation on next line)
603 * @return unescaped string or null
605 public static String getCSVString(String line, int[] next) {
607 if (i < 0 || (i = line.indexOf("\"", i)) < 0)
609 int pt = next[0] = i;
610 int len = line.length();
611 boolean escaped = false;
612 boolean haveEscape = false;
614 && (line.charAt(i) != '"' || (escaped = (i + 1 < len && line.charAt(i + 1) == '"'))))
622 return null; // unmatched
625 String s = line.substring(pt + 1, i);
626 return (haveEscape ? rep(rep(s, "\"\"", "\0"), "\0","\"") : s);
629 public static boolean isOneOf(String key, String semiList) {
630 if (semiList.length() == 0)
632 if (semiList.charAt(0) != ';')
633 semiList = ";" + semiList + ";";
634 return key.indexOf(";") < 0 && semiList.indexOf(';' + key + ';') >= 0;
637 public static String getQuotedAttribute(String info, String name) {
638 int i = info.indexOf(name + "=");
639 return (i < 0 ? null : getQuotedStringAt(info, i));
642 public static float approx(float f, float n) {
643 return Math.round (f * n) / n;
647 * Does a clean ITERATIVE replace of strFrom in str with strTo.
648 * Thus, rep("Testttt", "tt","t") becomes "Test".
653 * @return replaced string
655 public static String rep(String str, String strFrom, String strTo) {
656 if (str == null || strFrom.length() == 0 || str.indexOf(strFrom) < 0)
658 boolean isOnce = (strTo.indexOf(strFrom) >= 0);
660 str = str.replace(strFrom, strTo);
661 } while (!isOnce && str.indexOf(strFrom) >= 0);
665 public static String formatF(float value, int width, int precision,
666 boolean alignLeft, boolean zeroPad) {
667 return formatS(DF.formatDecimal(value, precision), width, 0, alignLeft, zeroPad);
677 * @param allowOverflow IGNORED
678 * @return formatted string
680 public static String formatD(double value, int width, int precision,
681 boolean alignLeft, boolean zeroPad, boolean allowOverflow) {
682 return formatS(DF.formatDecimal((float)value, -1 - precision), width, 0, alignLeft, zeroPad);
688 * @param width number of columns
689 * @param precision precision > 0 ==> precision = number of characters max from left
690 * precision < 0 ==> -1 - precision = number of char. max from right
692 * @param zeroPad generally for numbers turned strings
693 * @return formatted string
695 public static String formatS(String value, int width, int precision,
696 boolean alignLeft, boolean zeroPad) {
699 int len = value.length();
700 if (precision != Integer.MAX_VALUE && precision > 0
702 value = value.substring(0, precision);
703 else if (precision < 0 && len + precision >= 0)
704 value = value.substring(len + precision + 1);
706 int padLength = width - value.length();
709 boolean isNeg = (zeroPad && !alignLeft && value.charAt(0) == '-');
710 char padChar = (zeroPad ? '0' : ' ');
711 char padChar0 = (isNeg ? '-' : padChar);
716 sb.appendC(padChar0);
717 for (int i = padLength; --i > 0;)
718 // this is correct, not >= 0
721 sb.append(isNeg ? padChar + value.substring(1) : value);
722 return sb.toString();
726 * Does a clean replace of any of the characters in str with chrTo
727 * If strTo contains strFrom, then only a single pass is done.
728 * Otherwise, multiple passes are made until no more replacements can be made.
733 * @return replaced string
735 public static String replaceWithCharacter(String str, String strFrom,
739 for (int i = strFrom.length(); --i >= 0;)
740 str = str.replace(strFrom.charAt(i), chTo);
745 * Does a clean replace of any of the characters in str with strTo
746 * If strTo contains strFrom, then only a single pass is done.
747 * Otherwise, multiple passes are made until no more replacements can be made.
752 * @return replaced string
754 public static String replaceAllCharacters(String str, String strFrom,
756 for (int i = strFrom.length(); --i >= 0;) {
757 String chFrom = strFrom.substring(i, i + 1);
758 str = rep(str, chFrom, strTo);
763 public static String trim(String str, String chars) {
764 if (str == null || str.length() == 0)
766 if (chars.length() == 0)
768 int len = str.length();
770 while (k < len && chars.indexOf(str.charAt(k)) >= 0)
772 int m = str.length() - 1;
773 while (m > k && chars.indexOf(str.charAt(m)) >= 0)
775 return str.substring(k, m + 1);
778 public static String trimQuotes(String value) {
779 return (value != null && value.length() > 1 && value.startsWith("\"")
780 && value.endsWith("\"") ? value.substring(1, value.length() - 1)
784 public static boolean isNonStringPrimitive(Object info) {
785 // note that we don't use Double, Float, or Integer here
786 // because in JavaScript those would be false for unwrapped primitives
787 // coming from equivalent of Array.get()
788 // Strings will need their own escaped processing
790 return info instanceof Number || info instanceof Boolean;
793 // private static Object arrayGet(Object info, int i) {
796 // * Note that info will be a primitive in JavaScript
797 // * but a wrapped primitive in Java.
804 // return Array.get(info, i);
808 @SuppressWarnings("unchecked")
809 public static String toJSON(String infoType, Object info) {
811 return packageJSON(infoType, null);
812 if (isNonStringPrimitive(info))
813 return packageJSON(infoType, info.toString());
817 if (info instanceof String) {
822 * if (typeof s == "undefined") s = "null"
827 if (s.indexOf("{\"") != 0) {
828 //don't doubly fix JSON strings when retrieving status
829 // what about \1 \2 \3 etc.?
834 if (info instanceof JSONEncodable) {
835 // includes javajs.util.BS, org.jmol.script.SV
836 if ((s = ((JSONEncodable) info).toJSON()) == null)
837 s = "null"; // perhaps a list has a null value (group3List, for example)
841 if (info instanceof Map) {
844 for (String key : ((Map<String, ?>) info).keySet()) {
845 sb.append(sep).append(
846 packageJSON(key, toJSON(null, ((Map<?, ?>) info).get(key))));
852 if (info instanceof Lst) {
854 int n = ((Lst<?>) info).size();
855 for (int i = 0; i < n; i++) {
858 sb.append(toJSON(null, ((Lst<?>) info).get(i)));
863 if (info instanceof M34) {
865 int len = (info instanceof M4 ? 4 : 3);
866 float[] x = new float[len];
869 for (int i = 0; i < len; i++) {
873 sb.append(toJSON(null, x));
878 s = nonArrayString(info);
881 int n = AU.getLength(info);
882 for (int i = 0; i < n; i++) {
885 sb.append(toJSON(null, Array.get(info, i)));
890 info = info.toString();
892 return packageJSON(infoType, (s == null ? sb.toString() : s));
896 * Checks to see if an object is an array (including typed arrays), and if it is, returns null;
897 * otherwise it returns the string equivalent of that object.
900 * @return String or null
902 public static String nonArrayString(Object x) {
906 * return (x.constructor == Array || x.BYTES_PER_ELEMENT ? null : x.toString());
913 } catch (Exception e) {
919 public static String byteArrayToJSON(byte[] data) {
923 for (int i = 0; i < n; i++) {
926 sb.appendI(data[i] & 0xFF);
929 return sb.toString();
932 public static String packageJSON(String infoType, String info) {
933 return (infoType == null ? info : "\"" + infoType + "\": " + info);
936 public static String escapeUrl(String url) {
937 url = rep(url, "\n", "");
938 url = rep(url, "%", "%25");
939 url = rep(url, "#", "%23");
940 url = rep(url, "[", "%5B");
941 url = rep(url, "\\", "%5C");
942 url = rep(url, "]", "%5D");
943 url = rep(url, " ", "%20");
947 private final static String escapable = "\\\\\tt\rr\nn\"\"";
949 public static String esc(String str) {
950 if (str == null || str.length() == 0)
952 boolean haveEscape = false;
954 for (; i < escapable.length(); i += 2)
955 if (str.indexOf(escapable.charAt(i)) >= 0) {
960 while (i < escapable.length()) {
962 char ch = escapable.charAt(i++);
963 char ch2 = escapable.charAt(i++);
966 while ((pt = str.indexOf(ch, pt + 1)) >= 0) {
967 sb.append(str.substring(pt0, pt)).appendC('\\').appendC(ch2);
970 sb.append(str.substring(pt0, str.length()));
973 return "\"" + escUnicode(str) + "\"";
976 public static String escUnicode(String str) {
977 for (int i = str.length(); --i >= 0;)
978 if (str.charAt(i) > 0x7F) {
979 String s = "0000" + Integer.toHexString(str.charAt(i));
980 str = str.substring(0, i) + "\\u" + s.substring(s.length() - 4)
981 + str.substring(i + 1);
987 * ensures that a float turned to string has a decimal point
990 * @return string version of float
992 public static String escF(float f) {
998 * if (sf.indexOf(".") < 0 && sf.indexOf("e") < 0 && sf.indexOf("N") < 0 && sf.indexOf("n") < 0)
1005 public static String join(String[] s, char c, int i0) {
1010 for (int i = i0; i < s.length; i++)
1011 sb.appendC(c).append(s[i]);
1012 return sb.toString();
1016 * a LIKE "x" a is a string and equals x
1018 * a LIKE "*x" a is a string and ends with x
1020 * a LIKE "x*" a is a string and starts with x
1022 * a LIKE "*x*" a is a string and contains x
1028 public static boolean isLike(String a, String b) {
1029 boolean areEqual = a.equals(b);
1032 boolean isStart = b.startsWith("*");
1033 boolean isEnd = b.endsWith("*");
1034 return (!isStart && !isEnd) ? areEqual
1035 : isStart && isEnd ? b.length() == 1 || a.contains(b.substring(1, b.length() - 1))
1036 : isStart ? a.endsWith(b.substring(1))
1037 : a.startsWith(b.substring(0, b.length() - 1));
1040 public static Object getMapValueNoCase(Map<String, ?> h, String key) {
1041 if ("this".equals(key))
1043 Object val = h.get(key);
1045 for (Entry<String, ?> e : h.entrySet())
1046 if (e.getKey().equalsIgnoreCase(key))
1047 return e.getValue();
1051 public static String clean(String s) {
1052 return rep(replaceAllCharacters(s, " \t\n\r", " "), " ", " ").trim();
1057 * fdup duplicates p or q formats for formatCheck
1058 * and the format() function.
1063 * @return %3.5q%3.5q%3.5q%3.5q or %3.5p%3.5p%3.5p
1065 public static String fdup(String f, int pt, int n) {
1068 for (int i = pt; --i >= 1; ) {
1069 if (isDigit(ch = f.charAt(i)))
1077 if (i != 1 && f.charAt(i - 1) != '.')
1084 String s = f.substring(0, pt + 1);
1086 for (int i = 0; i < n; i++)
1088 sb.append(f.substring(pt + 1));
1089 return sb.toString();
1093 * generic string formatter based on formatLabel in Atom
1096 * @param strFormat .... %width.precisionKEY....
1097 * @param key any string to match
1098 * @param strT replacement string or null
1099 * @param floatT replacement float or Float.NaN
1100 * @param doubleT replacement double or Double.NaN -- for exponential
1101 * @param doOne mimic sprintf
1102 * @return formatted string
1105 private static String formatString(String strFormat, String key, String strT,
1106 float floatT, double doubleT, boolean doOne) {
1107 if (strFormat == null)
1109 if ("".equals(strFormat))
1111 int len = key.length();
1112 if (strFormat.indexOf("%") < 0 || len == 0 || strFormat.indexOf(key) < 0)
1115 String strLabel = "";
1116 int ich, ichPercent, ichKey;
1117 for (ich = 0; (ichPercent = strFormat.indexOf('%', ich)) >= 0
1118 && (ichKey = strFormat.indexOf(key, ichPercent + 1)) >= 0;) {
1119 if (ich != ichPercent)
1120 strLabel += strFormat.substring(ich, ichPercent);
1121 ich = ichPercent + 1;
1122 if (ichKey > ichPercent + 6) {
1127 boolean alignLeft = false;
1128 if (strFormat.charAt(ich) == '-') {
1132 boolean zeroPad = false;
1133 if (strFormat.charAt(ich) == '0') {
1139 while ((ch = strFormat.charAt(ich)) >= '0' && (ch <= '9')) {
1140 width = (10 * width) + (ch - '0');
1143 int precision = Integer.MAX_VALUE;
1144 boolean isExponential = false;
1145 if (strFormat.charAt(ich) == '.') {
1147 if ((ch = strFormat.charAt(ich)) == '-') {
1148 isExponential = (strT == null);
1151 if ((ch = strFormat.charAt(ich)) >= '0' && ch <= '9') {
1152 precision = ch - '0';
1156 precision = -precision;
1158 String st = strFormat.substring(ich, ich + len);
1159 if (!st.equals(key)) {
1160 ich = ichPercent + 1;
1165 if (!Float.isNaN(floatT)) // 'f'
1166 strLabel += formatF(floatT, width, precision, alignLeft,
1168 else if (strT != null) // 'd' 'i' or 's'
1169 strLabel += formatS(strT, width, precision, alignLeft,
1171 else if (!Double.isNaN(doubleT)) // 'e'
1172 strLabel += formatD(doubleT, width, precision - 1, alignLeft,
1176 } catch (IndexOutOfBoundsException ioobe) {
1181 strLabel += strFormat.substring(ich);
1182 //if (strLabel.length() == 0)
1187 public static String formatStringS(String strFormat, String key, String strT) {
1188 return formatString(strFormat, key, strT, Float.NaN, Double.NaN, false);
1191 public static String formatStringF(String strFormat, String key, float floatT) {
1192 return formatString(strFormat, key, null, floatT, Double.NaN, false);
1195 public static String formatStringI(String strFormat, String key, int intT) {
1196 return formatString(strFormat, key, "" + intT, Float.NaN, Double.NaN, false);
1200 * sprintf emulation uses (almost) c++ standard string formats
1202 * 's' string 'i' or 'd' integer, 'e' double, 'f' float, 'p' point3f 'q'
1203 * quaternion/plane/axisangle with added "i" (equal to the insipid "d" --
1208 * a listing of what sort of data will be found in Object[] values, in
1209 * order: s string, f float, i integer, d double, p point3f, q
1210 * quaternion/point4f, S String[], F float[], I int[], and D double[]
1212 * Object[] containing above types
1213 * @return formatted string
1215 public static String sprintf(String strFormat, String list, Object[] values) {
1218 int n = list.length();
1219 if (n == values.length)
1221 for (int o = 0; o < n; o++) {
1222 if (values[o] == null)
1224 switch (list.charAt(o)) {
1226 strFormat = formatString(strFormat, "s", (String) values[o],
1227 Float.NaN, Double.NaN, true);
1230 strFormat = formatString(strFormat, "f", null, ((Float) values[o])
1231 .floatValue(), Double.NaN, true);
1234 strFormat = formatString(strFormat, "d", "" + values[o], Float.NaN,
1236 strFormat = formatString(strFormat, "i", "" + values[o], Float.NaN,
1240 strFormat = formatString(strFormat, "e", null, Float.NaN,
1241 ((Double) values[o]).doubleValue(), true);
1244 T3 pVal = (T3) values[o];
1245 strFormat = formatString(strFormat, "p", null, pVal.x, Double.NaN,
1247 strFormat = formatString(strFormat, "p", null, pVal.y, Double.NaN,
1249 strFormat = formatString(strFormat, "p", null, pVal.z, Double.NaN,
1253 T4 qVal = (T4) values[o];
1254 strFormat = formatString(strFormat, "q", null, qVal.x, Double.NaN,
1256 strFormat = formatString(strFormat, "q", null, qVal.y, Double.NaN,
1258 strFormat = formatString(strFormat, "q", null, qVal.z, Double.NaN,
1260 strFormat = formatString(strFormat, "q", null, qVal.w, Double.NaN,
1264 String[] sVal = (String[]) values[o];
1265 for (int i = 0; i < sVal.length; i++)
1266 strFormat = formatString(strFormat, "s", sVal[i], Float.NaN,
1270 float[] fVal = (float[]) values[o];
1271 for (int i = 0; i < fVal.length; i++)
1272 strFormat = formatString(strFormat, "f", null, fVal[i],
1276 int[] iVal = (int[]) values[o];
1277 for (int i = 0; i < iVal.length; i++)
1278 strFormat = formatString(strFormat, "d", "" + iVal[i], Float.NaN,
1280 for (int i = 0; i < iVal.length; i++)
1281 strFormat = formatString(strFormat, "i", "" + iVal[i], Float.NaN,
1285 double[] dVal = (double[]) values[o];
1286 for (int i = 0; i < dVal.length; i++)
1287 strFormat = formatString(strFormat, "e", null, Float.NaN,
1292 return rep(strFormat, "%%", "%");
1293 } catch (Exception e) {
1296 System.out.println("TextFormat.sprintf error " + list + " " + strFormat);
1297 return rep(strFormat, "%", "?");
1302 * formatCheck checks p and q formats and duplicates if necessary
1303 * "%10.5p xxxx" ==> "%10.5p%10.5p%10.5p xxxx"
1306 * @return f or dupicated format
1308 public static String formatCheck(String strFormat) {
1309 if (strFormat == null || strFormat.indexOf('p') < 0 && strFormat.indexOf('q') < 0)
1311 strFormat = rep(strFormat, "%%", "\1");
1312 strFormat = rep(strFormat, "%p", "%6.2p");
1313 strFormat = rep(strFormat, "%q", "%6.2q");
1314 String[] format = split(strFormat, "%");
1316 sb.append(format[0]);
1317 for (int i = 1; i < format.length; i++) {
1318 String f = "%" + format[i];
1320 if (f.length() >= 3) {
1321 if ((pt = f.indexOf('p')) >= 0)
1323 if ((pt = f.indexOf('q')) >= 0)
1328 return sb.toString().replace('\1', '%');
1331 public static void leftJustify(SB s, String s1, String s2) {
1333 int n = s1.length() - s2.length();
1335 s.append(s1.substring(0, n));
1338 public static void rightJustify(SB s, String s1, String s2) {
1339 int n = s1.length() - s2.length();
1341 s.append(s1.substring(0, n));
1345 public static String safeTruncate(float f, int n) {
1346 if (f > -0.001 && f < 0.001)
1348 return (f + " ").substring(0,n);
1351 public static boolean isWild(String s) {
1352 return s != null && (s.indexOf("*") >= 0 || s.indexOf("?") >= 0);
1356 * A general non-regex (for performance) text matcher that utilizes ? and *.
1358 * ??? means "at most three" characters if at beginning or end;
1359 * "exactly three" otherwise
1360 * \1 in search is a stand-in for actual ?
1363 * the string to search
1367 * @param allowInitialStar
1368 * @return true if found
1370 public static boolean isMatch(String search, String match, boolean checkStar,
1371 boolean allowInitialStar) {
1372 // search == match --> true
1373 if (search.equals(match))
1375 int mLen = match.length();
1376 // match == "" --> false
1379 boolean isStar0 = (checkStar && allowInitialStar ? match.charAt(0) == '*'
1381 // match == "*" --> true
1382 if (mLen == 1 && isStar0)
1384 boolean isStar1 = (checkStar && match.endsWith("*"));
1385 boolean haveQ = (match.indexOf('?') >= 0);
1386 // match == "**" --> true
1387 // match == "*xxx*" --> search contains "xxx"
1388 // match == "*xxx" --> search ends with "xxx"
1389 // match == "xxx*" --> search starts with "xxx"
1392 return (isStar1 ? (mLen < 3 || search.indexOf(match.substring(1,
1393 mLen - 1)) >= 0) : search.endsWith(match.substring(1)));
1395 return search.startsWith(match.substring(0, mLen - 1));
1397 int sLen = search.length();
1398 // pad match with "?" -- same as *
1399 String qqqq = "????";
1407 match = qqqq + match.substring(1);
1411 match = match.substring(0, mLen - 1) + qqqq;
1415 // length of match < length of search --> false
1419 // -- each ? matches ONE character if not at end
1420 // -- extra ? at end ignored
1422 // (allowInitialStar == true)
1423 // -- extra ? at beginning reduced to match length
1426 while (mLen > sLen) {
1427 if (allowInitialStar && match.charAt(ich) == '?') {
1429 } else if (match.charAt(ich + mLen - 1) != '?') {
1435 // both are effectively same length now.
1436 // \1 is stand-in for "?"
1438 for (int i = sLen; --i >= 0;) {
1439 char chm = match.charAt(ich + i);
1442 char chs = search.charAt(i);
1443 if (chm != chs && (chm != '\1' || chs != '?'))
1449 public static String replaceQuotedStrings(String s, Lst<String> list,
1450 Lst<String> newList) {
1451 int n = list.size();
1452 for (int i = 0; i < n; i++) {
1453 String name = list.get(i);
1454 String newName = newList.get(i);
1455 if (!newName.equals(name))
1456 s = rep(s, "\"" + name + "\"", "\"" + newName
1462 public static String replaceStrings(String s, Lst<String> list,
1463 Lst<String> newList) {
1464 int n = list.size();
1465 for (int i = 0; i < n; i++) {
1466 String name = list.get(i);
1467 String newName = newList.get(i);
1468 if (!newName.equals(name))
1469 s = rep(s, name, newName);
1474 public static boolean isDigit(char ch) {
1475 // just way simpler code than Character.isDigit(ch);
1477 return (48 <= c && c <= 57);
1480 public static boolean isUpperCase(char ch) {
1482 return (65 <= c && c <= 90);
1485 public static boolean isLowerCase(char ch) {
1487 return (97 <= c && c <= 122);
1490 public static boolean isLetter(char ch) {
1491 // just way simpler code than Character.isLetter(ch);
1493 return (65 <= c && c <= 90 || 97 <= c && c <= 122);
1496 public static boolean isLetterOrDigit(char ch) {
1497 // just way simpler code than Character.isLetterOrDigit(ch);
1499 return (65 <= c && c <= 90 || 97 <= c && c <= 122 || 48 <= c && c <= 57);
1502 public static boolean isWhitespace(char ch) {
1504 return (c >= 0x1c && c <= 0x20 || c >= 0x9 && c <= 0xd);
1507 public static final float FRACTIONAL_PRECISION = 100000f;
1508 public static final float CARTESIAN_PRECISION = 10000f;
1510 public static void fixPtFloats(T3 pt, float f) {
1511 //this will equate float and double as long as -256 <= x <= 256
1512 pt.x = Math.round(pt.x * f) / f;
1513 pt.y = Math.round(pt.y * f) / f;
1514 pt.z = Math.round(pt.z * f) / f;
1517 public static double fixDouble(double d, double f) {
1518 return Math.round(d * f) / f;
1522 * parse a float or "float/float"
1526 public static float parseFloatFraction(String s) {
1527 int pt = s.indexOf("/");
1528 return (pt < 0 ? parseFloat(s) : parseFloat(s.substring(0, pt))
1529 / parseFloat(s.substring(pt + 1)));
1534 // double d = 790.8999998888;
1535 // float x = 790.8999998888f;
1536 // for (int i = 0; i < 50; i++) {
1537 // System.out.println(x + " " + d);
1538 // System.out.println(Math.round(x * 100000) / 100000f);
1539 // System.out.println(Math.round(d * 100000) / 100000.);
1540 // System.out.println(Math.round(x * 10000) / 10000f);
1541 // System.out.println(Math.round(d * 10000) / 10000.);
1545 // System.out.println(100.123456789f);
1551 // t = System.currentTimeMillis();
1552 // for (int i = 0; i < 10000000; i++) {
1553 // boolean b = PT.isUpperCase(c);
1555 // System.out.println(System.currentTimeMillis() - t);
1557 // t = System.currentTimeMillis();
1558 // for (int i = 0; i < 10000000; i++) {
1559 // boolean b = Character.isUpperCase(c);
1561 // System.out.println(System.currentTimeMillis() - t);
1563 // t = System.currentTimeMillis();
1564 // for (int i = 0; i < 10000000; i++) {
1565 // boolean b = PT.isUpperCase(c);
1567 // System.out.println(System.currentTimeMillis() - t);
1569 // System.out.println("PT test");