Merge branch 'features/r2_11_2/JAL-3821_reinstate_patch' into develop
[jalview.git] / unused / javajs / util / PT.java
1 /* $RCSfile$
2  * $Author: hansonr $
3  * $Date: 2007-04-26 16:57:51 -0500 (Thu, 26 Apr 2007) $
4  * $Revision: 7502 $
5  *
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.
8  *
9  * Copyright (C) 2005  The Jmol Development Team
10  *
11  * Contact: jmol-developers@lists.sf.net
12  *
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.
17  *
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.
22  *
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.
26  */
27
28 package javajs.util;
29
30 import java.lang.reflect.Array;
31 import java.util.Map;
32 import java.util.Map.Entry;
33
34 import javajs.api.JSONEncodable;
35
36 /**
37  * a combination of Parsing and Text-related utility classes
38  * 
39  * @author hansonr
40  * 
41  */
42
43 public class PT {
44
45   public static int parseInt(String str) {
46     return parseIntNext(str, new int[] {0});
47   }
48
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);
54   }
55
56   public static int parseIntChecked(String str, int ichMax, int[] next) {
57     boolean digitSeen = false;
58     int value = 0;
59     int ich = next[0];
60     if (ich < 0)
61       return Integer.MIN_VALUE;
62     int ch;
63     while (ich < ichMax && isWhiteSpace(str, ich))
64       ++ich;
65     boolean negative = false;
66     if (ich < ichMax && str.charAt(ich) == 45) { //"-"
67       negative = true;
68       ++ich;
69     }
70     while (ich < ichMax && (ch = str.charAt(ich)) >= 48 && ch <= 57) {
71       value = value * 10 + (ch - 48);
72       digitSeen = true;
73       ++ich;
74     }
75     if (!digitSeen)// || !checkTrailingText(str, ich, ichMax))
76       value = Integer.MIN_VALUE;
77     else if (negative)
78       value = -value;
79     next[0] = ich;
80     return value;
81   }
82
83   public static boolean isWhiteSpace(String str, int ich) {
84     char ch;
85     return (ich >= 0 && ((ch = str.charAt(ich)) == ' ' || ch == '\t' || ch == '\n'));
86   }
87
88   /**
89    * A float parser that is 30% faster than Float.parseFloat(x) and also accepts
90    * x.yD+-n
91    * 
92    * @param str
93    * @param ichMax
94    * @param next
95    *        pointer; incremented
96    * @param isStrict
97    * @return value or Float.NaN
98    */
99   public static float parseFloatChecked(String str, int ichMax, int[] next,
100                                          boolean isStrict) {
101     boolean digitSeen = false;
102     int ich = next[0];
103     if (isStrict && str.indexOf('\n') != str.lastIndexOf('\n'))
104       return Float.NaN;
105     while (ich < ichMax && isWhiteSpace(str, ich))
106       ++ich;
107     boolean negative = false;
108     if (ich < ichMax && str.charAt(ich) == '-') {
109       ++ich;
110       negative = true;
111     }
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
115     
116     // This is Safari figuring out the values of the numbers on the line (x, y, then z):
117   
118     //  ATOM 1241 CD1 LEU A 64 -2.206 36.532 31.576 1.00 60.60 C
119     //  e=1408749273
120     //  -e =-1408749273
121     //  ATOM 1241 CD1 LEU A 64 -2.206 36.532 31.576 1.00 60.60 C
122     //  e=-1821066134
123     //  e=36.532
124     //  ATOM 1241 CD1 LEU A 64 -2.206 36.532 31.576 1.00 60.60 C
125     //  e=-1133871366
126     //  e=31.576
127     //
128     //  "e" values are just before and after the "value = -value" statement.
129     
130     int ch = 0;
131     float ival = 0f;
132     float ival2 = 0f;
133     while (ich < ichMax && (ch = str.charAt(ich)) >= 48 && ch <= 57) {
134       ival = (ival * 10f) + (ch - 48)*1f;
135       ++ich;
136       digitSeen = true;
137     }
138     boolean isDecimal = false;
139     int iscale = 0;
140     int nzero = (ival == 0 ? -1 : 0);
141     if (ch == '.') {
142       isDecimal = true;
143       while (++ich < ichMax && (ch = str.charAt(ich)) >= 48 && ch <= 57) {
144         digitSeen = true;
145         if (nzero < 0) {
146           if (ch == 48) { 
147             nzero--;
148             continue;
149           }
150           nzero = -nzero;
151         } 
152         if (iscale  < decimalScale.length) {
153           ival2 = (ival2 * 10f) + (ch - 48)*1f;
154           iscale++;
155         }
156       }
157     }
158     float value;
159     
160     // Safari breaks here intermittently converting integers to floats 
161     
162     if (!digitSeen) {
163       value = Float.NaN;
164     } else if (ival2 > 0) {
165       value = ival2 * decimalScale[iscale - 1];
166       if (nzero > 1) {
167         if (nzero - 2 < decimalScale.length) {
168           value *= decimalScale[nzero - 2];
169         } else {
170           value *= Math.pow(10, 1 - nzero);
171         }
172       } else {
173         value += ival;
174       }
175     } else {
176       value = ival;
177     }
178     boolean isExponent = false;
179     if (ich < ichMax && (ch == 69 || ch == 101 || ch == 68)) { // E e D
180       isExponent = true;
181       if (++ich >= ichMax)
182         return Float.NaN;
183       ch = str.charAt(ich);
184       if ((ch == '+') && (++ich >= ichMax))
185         return Float.NaN;
186       next[0] = ich;
187       int exponent = parseIntChecked(str, ichMax, next);
188       if (exponent == Integer.MIN_VALUE)
189         return Float.NaN;
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);
196     } else {
197       next[0] = ich; // the exponent code finds its own ichNextParse
198     }
199     // believe it or not, Safari reports the long-equivalent of the 
200     // float value here, then later the float value, after no operation!
201     if (negative)
202       value = -value;
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);
207   }
208
209   public final static float[] tensScale = { 10f, 100f, 1000f, 10000f, 100000f, 1000000f };
210   public final static float[] decimalScale = { 
211   0.1f, 
212   0.01f, 
213   0.001f, 
214   0.0001f, 
215   0.00001f,
216   0.000001f, 
217   0.0000001f, 
218   0.00000001f, 
219   0.000000001f
220   };
221   public static boolean checkTrailingText(String str, int ich, int ichMax) {
222     //number must be pure -- no additional characters other than white space or ;
223     char ch;
224     while (ich < ichMax && (isWhitespace(ch = str.charAt(ich)) || ch == ';'))
225       ++ich;
226     return (ich == ichMax);
227   }
228
229   public static float[] parseFloatArray(String str) {
230     return parseFloatArrayNext(str, new int[1], null, null, null);
231   }
232
233   public static int parseFloatArrayInfested(String[] tokens, float[] data) {
234     int len = data.length;
235     int nTokens = tokens.length;
236     int n = 0;
237     int max = 0;
238     for (int i = 0; i >= 0 && i < len && n < nTokens; i++) {
239       float f;
240       while (Float.isNaN(f = parseFloat(tokens[n++])) 
241           && n < nTokens) {
242       }
243       if (!Float.isNaN(f))
244         data[(max = i)] = f;
245       if (n == nTokens)
246         break;
247     }
248     return max + 1;
249   }
250
251   /**
252    * @param str
253    * @param next
254    * @param f
255    * @param strStart or null
256    * @param strEnd   or null
257    * @return array of float values
258    * 
259    */
260   public static float[] parseFloatArrayNext(String str, int[] next, float[] f,
261                                             String strStart, String strEnd) {
262     int n = 0;
263     int pt = next[0];
264     if (pt >= 0) {
265       if (strStart != null) {
266         int p = str.indexOf(strStart, pt);
267         if (p >= 0)
268           next[0] = p + strStart.length();
269       }
270       str = str.substring(next[0]);
271       pt = (strEnd == null ? -1 : str.indexOf(strEnd));
272       if (pt < 0)
273         pt = str.length();
274       else
275         str = str.substring(0, pt);
276       next[0] += pt + 1;
277       String[] tokens = getTokens(str);
278       if (f == null)
279         f = new float[tokens.length];
280       n = parseFloatArrayInfested(tokens, f);
281     }
282     if (f == null)
283       return new float[0];
284     for (int i = n; i < f.length; i++)
285       f[i] = Float.NaN;
286     return f;
287   }
288
289   public static float parseFloatRange(String str, int ichMax, int[] next) {
290     int cch = str.length();
291     if (ichMax > cch)
292       ichMax = cch;
293     if (next[0] < 0 || next[0] >= ichMax)
294       return Float.NaN;
295     return parseFloatChecked(str, ichMax, next, false);
296   }
297
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));
301   }
302
303   public static float parseFloatStrict(String str) {
304     // checks trailing characters and does not allow "1E35" to be float
305     int cch = str.length();
306     if (cch == 0)
307       return Float.NaN;
308     return parseFloatChecked(str, cch, new int[] {0}, true);
309   }
310
311   public static float parseFloat(String str) {
312     return parseFloatNext(str, new int[] {0});
313   }
314
315   public static int parseIntRadix(String s, int i) throws NumberFormatException {
316 //    /**
317 //     * 
318 //     * JavaScript uses parseIntRadix
319 //     * 
320 //     * @j2sNative
321 //     * 
322 //     *    return Integer.parseIntRadix(s, i);
323 //     *    
324 //     */
325 //    {
326       return Integer.parseInt(s, i);
327 //    }
328   }
329
330   public static String[] getTokens(String line) {
331     return getTokensAt(line, 0);
332   }
333
334   public static String parseToken(String str) {
335     return parseTokenNext(str, new int[] {0});
336   }
337
338   public static String parseTrimmed(String str) {
339     return parseTrimmedRange(str, 0, str.length());
340   }
341
342   public static String parseTrimmedAt(String str, int ichStart) {
343     return parseTrimmedRange(str, ichStart, str.length());
344   }
345
346   public static String parseTrimmedRange(String str, int ichStart, int ichMax) {
347     int cch = str.length();
348     if (ichMax < cch)
349       cch = ichMax;
350     if (cch < ichStart)
351       return "";
352     return parseTrimmedChecked(str, ichStart, cch);
353   }
354
355   public static String[] getTokensAt(String line, int ich) {
356     if (line == null)
357       return null;
358     int cchLine = line.length();
359     if (ich < 0 || ich > cchLine)
360       return null;
361     int tokenCount = countTokens(line, ich);
362     String[] tokens = new String[tokenCount];
363     int[] next = new int[1];
364     next[0] = ich;
365     for (int i = 0; i < tokenCount; ++i)
366       tokens[i] = parseTokenChecked(line, cchLine, next);
367     return tokens;
368   }
369
370   public static int countChar(String line, char c) {
371     int n = 0;
372     for (int i = line.lastIndexOf(c) + 1; --i >= 0;)
373       if (line.charAt(i) == c)
374         n++;
375     return n;
376   }
377   
378   public static int countTokens(String line, int ich) {
379     int tokenCount = 0;
380     if (line != null) {
381       int ichMax = line.length();
382       while (true) {
383         while (ich < ichMax && isWhiteSpace(line, ich))
384           ++ich;
385         if (ich == ichMax)
386           break;
387         ++tokenCount;
388         do {
389           ++ich;
390         } while (ich < ichMax && !isWhiteSpace(line, ich));
391       }
392     }
393     return tokenCount;
394   }
395
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));
399   }
400
401   public static String parseTokenRange(String str, int ichMax, int[] next) {
402     int cch = str.length();
403     if (ichMax > cch)
404       ichMax = cch;
405     return (next[0] < 0 || next[0] >= ichMax ? null : parseTokenChecked(str, ichMax, next));
406   }
407
408   public static String parseTokenChecked(String str, int ichMax, int[] next) {
409     int ich = next[0];
410     while (ich < ichMax && isWhiteSpace(str, ich))
411       ++ich;
412     int ichNonWhite = ich;
413     while (ich < ichMax && !isWhiteSpace(str, ich))
414       ++ich;
415     next[0] = ich;
416     return (ichNonWhite == ich ? null : str.substring(ichNonWhite, ich));
417   }
418
419   public static String parseTrimmedChecked(String str, int ich, int ichMax) {
420     while (ich < ichMax && isWhiteSpace(str, ich))
421       ++ich;
422     int ichLast = ichMax - 1;
423     while (ichLast >= ich && isWhiteSpace(str, ichLast))
424       --ichLast;
425     return (ichLast < ich ? "" : str.substring(ich, ichLast + 1));
426   }
427
428 //  public static double dVal(String s) throws NumberFormatException {
429 //    /**
430 //     * @j2sNative
431 //     * 
432 //     * if(s==null)
433 //     *   throw new NumberFormatException("null");
434 //     * var d=parseFloat(s);
435 //     * if(isNaN(d))
436 //     *  throw new NumberFormatException("Not a Number : "+s);
437 //     * return d 
438 //     * 
439 //     */
440 //    {
441 //      return Double.valueOf(s).doubleValue();
442 //    }
443 //  }
444 //
445 //  public static float fVal(String s) throws NumberFormatException {
446 //    /**
447 //     * @j2sNative
448 //     * 
449 //     * return this.dVal(s);
450 //     */
451 //    {
452 //      
453 //      return Float.parseFloat(s);
454 //    }
455 //  }
456
457   public static int parseIntRange(String str, int ichMax, int[] next) {
458     int cch = str.length();
459     if (ichMax > cch)
460       ichMax = cch;
461     return (next[0] < 0 || next[0] >= ichMax ? Integer.MIN_VALUE : parseIntChecked(str, ichMax, next));
462   }
463
464   /**
465    * parses a string array for floats. Returns NaN for nonfloats.
466    * 
467    *  @param tokens  the strings to parse
468    *  @param data    the array to fill
469    */
470   public static void parseFloatArrayData(String[] tokens, float[] data) {
471     parseFloatArrayDataN(tokens, data, data.length);
472   }
473
474   /**
475    * parses a string array for floats. Returns NaN for nonfloats or missing data.
476    * 
477    *  @param tokens  the strings to parse
478    *  @param data    the array to fill
479    *  @param nData   the number of elements
480    */
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]));
484   }
485
486   /**
487    * 
488    *  proper splitting, even for Java 1.3 -- if the text ends in the run,
489    *  no new line is appended.
490    * 
491    * @param text
492    * @param run
493    * @return  String array
494    */
495   public static String[] split(String text, String run) {
496     if (text.length() == 0)
497       return new String[0];
498     int n = 1;
499     int i = text.indexOf(run);
500     String[] lines;
501     int runLen = run.length();
502     if (i < 0 || runLen == 0) {
503       lines = new String[1];
504       lines[0] = text;
505       return lines;
506     }
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];
511     i = 0;
512     int ipt = 0;
513     int pt = 0;
514     for (; (ipt = text.indexOf(run, i)) >= 0 && pt + 1 < n;) {
515       lines[pt++] = text.substring(i, ipt);
516       i = ipt + runLen;
517     }
518     if (text.indexOf(run, len) != len)
519       len += runLen;
520     lines[pt] = text.substring(i, len);
521     return lines;
522   }
523
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
526   
527   /// general static string-parsing class ///
528
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.
531
532 //  public static String concatTokens(String[] tokens, int iFirst, int iEnd) {
533 //    String str = "";
534 //    String sep = "";
535 //    for (int i = iFirst; i < iEnd; i++) {
536 //      if (i < tokens.length) {
537 //        str += sep + tokens[i];
538 //        sep = " ";
539 //      }
540 //    }
541 //    return str;
542 //  }
543   
544   public static String getQuotedStringAt(String line, int ipt0) {
545     int[] next = new int[] { ipt0 };
546     return getQuotedStringNext(line, next);
547   }
548   
549   /**
550    * 
551    * @param line
552    * @param next passes [current pointer]
553    * @return quoted string -- does NOT unescape characters
554    */
555   public static String getQuotedStringNext(String line, int[] next) {
556     int i = next[0];
557     if (i < 0 || (i = line.indexOf("\"", i)) < 0)
558       return "";
559     int pt = i + 1;
560     int len = line.length();
561     while (++i < len && line.charAt(i) != '"')
562       if (line.charAt(i) == '\\')
563         i++;
564     next[0] = i + 1;
565     return line.substring(pt, i);
566   }
567   
568   /**
569    * single- or double-quoted string or up to the first space -- like HTML5
570    * not case-sensitive
571    * 
572    * @param line
573    * @param key
574    * @return attribute
575    */
576   public static String getQuotedOrUnquotedAttribute(String line, String key) {
577     if (line == null || key == null)
578       return null;
579     int pt = line.toLowerCase().indexOf(key.toLowerCase() + "=");
580     if (pt < 0 || (pt = pt + key.length() + 1) >= line.length())
581       return "";
582     char c = line.charAt(pt);
583     switch (c) {
584     case '\'':
585     case '"':
586       pt++;
587       break;
588     default:
589       c = ' ';
590       line += " ";
591     }
592     int pt1 = line.indexOf(c, pt);
593     return (pt1 < 0 ? null : line.substring(pt, pt1));
594   }
595   
596   /**
597    * CSV format -- escaped quote is "" WITHIN "..."
598    *
599    * 
600    * @param line
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
604    */
605   public static String getCSVString(String line, int[] next) {
606     int i = next[1];
607     if (i < 0 || (i = line.indexOf("\"", i)) < 0)
608       return null;
609     int pt = next[0] = i;
610     int len = line.length();
611     boolean escaped = false;
612     boolean haveEscape = false;
613     while (++i < len 
614         && (line.charAt(i) != '"' || (escaped = (i + 1 < len && line.charAt(i + 1) == '"'))))
615       if (escaped) {
616         escaped = false;
617         haveEscape = true;
618         i++;
619       }
620     if (i >= len) {
621       next[1] = -1;
622       return null; // unmatched
623     }
624     next[1] = i + 1;
625     String s = line.substring(pt + 1, i);
626     return (haveEscape ? rep(rep(s, "\"\"", "\0"), "\0","\"") : s);
627   }
628   
629   public static boolean isOneOf(String key, String semiList) {
630     if (semiList.length() == 0)
631       return false;
632     if (semiList.charAt(0) != ';')
633       semiList = ";" + semiList + ";";
634     return key.indexOf(";") < 0  && semiList.indexOf(';' + key + ';') >= 0;
635   }
636
637   public static String getQuotedAttribute(String info, String name) {
638     int i = info.indexOf(name + "=");
639     return (i < 0 ? null : getQuotedStringAt(info, i));
640   }
641
642   public static float approx(float f, float n) {
643     return Math.round (f * n) / n;
644   }
645
646   /**
647    * Does a clean ITERATIVE replace of strFrom in str with strTo. 
648    * Thus, rep("Testttt", "tt","t") becomes "Test".
649    * 
650    * @param str
651    * @param strFrom
652    * @param strTo
653    * @return replaced string
654    */
655   public static String rep(String str, String strFrom, String strTo) {
656     if (str == null || strFrom.length() == 0 || str.indexOf(strFrom) < 0)
657       return str;
658     boolean isOnce = (strTo.indexOf(strFrom) >= 0);
659     do {
660       str = str.replace(strFrom, strTo);
661     } while (!isOnce && str.indexOf(strFrom) >= 0);
662     return str;
663   }
664
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);
668   }
669
670   /**
671    * 
672    * @param value
673    * @param width
674    * @param precision
675    * @param alignLeft
676    * @param zeroPad
677    * @param allowOverflow IGNORED
678    * @return formatted string
679    */
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);
683   }
684
685   /**
686    * 
687    * @param value       
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
691    * @param alignLeft
692    * @param zeroPad     generally for numbers turned strings
693    * @return            formatted string
694    */
695   public static String formatS(String value, int width, int precision,
696                               boolean alignLeft, boolean zeroPad) {
697     if (value == null)
698       return "";
699     int len = value.length();
700     if (precision != Integer.MAX_VALUE && precision > 0
701         && precision < len)
702       value = value.substring(0, precision);
703     else if (precision < 0 && len + precision >= 0)
704       value = value.substring(len + precision + 1);
705   
706     int padLength = width - value.length();
707     if (padLength <= 0)
708       return value;
709     boolean isNeg = (zeroPad && !alignLeft && value.charAt(0) == '-');
710     char padChar = (zeroPad ? '0' : ' ');
711     char padChar0 = (isNeg ? '-' : padChar);
712   
713     SB sb = new SB();
714     if (alignLeft)
715       sb.append(value);
716     sb.appendC(padChar0);
717     for (int i = padLength; --i > 0;)
718       // this is correct, not >= 0
719       sb.appendC(padChar);
720     if (!alignLeft)
721       sb.append(isNeg ? padChar + value.substring(1) : value);
722     return sb.toString();
723   }
724
725   /**
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.
729    * 
730    * @param str
731    * @param strFrom
732    * @param chTo
733    * @return  replaced string
734    */
735   public static String replaceWithCharacter(String str, String strFrom,
736                                             char chTo) {
737     if (str == null)
738       return null;
739     for (int i = strFrom.length(); --i >= 0;)
740       str = str.replace(strFrom.charAt(i), chTo);
741     return str;
742   }
743
744   /**
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.
748    * 
749    * @param str
750    * @param strFrom
751    * @param strTo
752    * @return  replaced string
753    */
754   public static String replaceAllCharacters(String str, String strFrom,
755                                             String strTo) {
756     for (int i = strFrom.length(); --i >= 0;) {
757       String chFrom = strFrom.substring(i, i + 1);
758       str = rep(str, chFrom, strTo);
759     }
760     return str;
761   }
762
763   public static String trim(String str, String chars) {
764     if (str == null || str.length() == 0)
765       return str;
766     if (chars.length() == 0)
767       return str.trim();
768     int len = str.length();
769     int k = 0;
770     while (k < len && chars.indexOf(str.charAt(k)) >= 0)
771       k++;
772     int m = str.length() - 1;
773     while (m > k && chars.indexOf(str.charAt(m)) >= 0)
774       m--;
775     return str.substring(k, m + 1);
776   }
777
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)
781         : value);
782   }
783
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
789     
790     return info instanceof Number || info instanceof Boolean;
791   }
792
793 //  private static Object arrayGet(Object info, int i) {
794 //    /**
795 //     * 
796 //     * Note that info will be a primitive in JavaScript
797 //     * but a wrapped primitive in Java.
798 //     * 
799 //     * @j2sNative
800 //     * 
801 //     *            return info[i];
802 //     */
803 //    {
804 //      return Array.get(info, i);
805 //    }
806 //  }
807 //  
808   @SuppressWarnings("unchecked")
809   public static String toJSON(String infoType, Object info) {
810     if (info == null)
811       return packageJSON(infoType, null);
812     if (isNonStringPrimitive(info))
813       return packageJSON(infoType, info.toString());
814     String s = null;
815     SB sb = null;
816     while (true) {
817       if (info instanceof String) {
818         s = (String) info;
819         /**
820          * @j2sNative
821          * 
822          * if (typeof s == "undefined") s = "null"
823          * 
824          */
825         {}
826         
827         if (s.indexOf("{\"") != 0) {
828           //don't doubly fix JSON strings when retrieving status
829           // what about  \1 \2 \3 etc.?
830           s = esc(s);
831         }
832         break;
833       }
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)
838         break;
839       }
840       sb = new SB();
841       if (info instanceof Map) {
842         sb.append("{ ");
843         String sep = "";
844         for (String key : ((Map<String, ?>) info).keySet()) {
845           sb.append(sep).append(
846               packageJSON(key, toJSON(null, ((Map<?, ?>) info).get(key))));
847           sep = ",";
848         }
849         sb.append(" }");
850         break;
851       }
852       if (info instanceof Lst) {
853         sb.append("[ ");
854         int n = ((Lst<?>) info).size();
855         for (int i = 0; i < n; i++) {
856           if (i > 0)
857             sb.appendC(',');
858           sb.append(toJSON(null, ((Lst<?>) info).get(i)));
859         }
860         sb.append(" ]");
861         break;
862       }
863       if (info instanceof M34) {
864         // M4 extends M3
865         int len = (info instanceof M4 ? 4 : 3);
866         float[] x = new float[len];
867         M34 m = (M34) info;
868         sb.appendC('[');
869         for (int i = 0; i < len; i++) {
870           if (i > 0)
871             sb.appendC(',');
872           m.getRow(i, x);
873           sb.append(toJSON(null, x));
874         }
875         sb.appendC(']');
876         break;
877       }
878       s = nonArrayString(info);
879       if (s == null) {
880         sb.append("[");
881         int n = AU.getLength(info);
882         for (int i = 0; i < n; i++) {
883           if (i > 0)
884             sb.appendC(',');
885           sb.append(toJSON(null, Array.get(info, i)));
886         }
887         sb.append("]");
888         break;
889       }
890       info = info.toString();
891     }
892     return packageJSON(infoType, (s == null ? sb.toString() : s));
893   }
894
895   /**
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.
898    * 
899    * @param x
900    * @return String or null
901    */
902   public static String nonArrayString(Object x) {
903     /**
904      * @j2sNative
905      * 
906      * return (x.constructor == Array || x.BYTES_PER_ELEMENT ? null : x.toString());
907      * 
908      */
909     {
910       try {
911         Array.getLength(x);
912         return null;
913       } catch (Exception e) {
914         return x.toString();
915       }
916     }
917   }
918
919   public static String byteArrayToJSON(byte[] data) {
920     SB sb = new SB();
921     sb.append("[");
922     int n = data.length;
923     for (int i = 0; i < n; i++) {
924       if (i > 0)
925         sb.appendC(',');
926       sb.appendI(data[i] & 0xFF);
927     }
928     sb.append("]");
929     return sb.toString();
930   }
931   
932   public static String packageJSON(String infoType, String info) {
933     return (infoType == null ? info : "\"" + infoType + "\": " + info);
934   }
935
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");
944     return url;
945   }
946
947   private final static String escapable = "\\\\\tt\rr\nn\"\""; 
948
949   public static String esc(String str) {
950     if (str == null || str.length() == 0)
951       return "\"\"";
952     boolean haveEscape = false;
953     int i = 0;
954     for (; i < escapable.length(); i += 2)
955       if (str.indexOf(escapable.charAt(i)) >= 0) {
956         haveEscape = true;
957         break;
958       }
959     if (haveEscape)
960       while (i < escapable.length()) {
961         int pt = -1;
962         char ch = escapable.charAt(i++);
963         char ch2 = escapable.charAt(i++);
964         SB sb = new SB();
965         int pt0 = 0;
966         while ((pt = str.indexOf(ch, pt + 1)) >= 0) {
967           sb.append(str.substring(pt0, pt)).appendC('\\').appendC(ch2);
968           pt0 = pt + 1;
969         }
970         sb.append(str.substring(pt0, str.length()));
971         str = sb.toString();
972       }    
973     return "\"" + escUnicode(str) + "\"";
974   }
975
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);
982       }
983     return str;
984   }
985
986   /**
987    * ensures that a float turned to string has a decimal point
988    * 
989    * @param f
990    * @return string version of float
991    */
992   public static String escF(float f) {
993     String sf = "" + f;
994     // NaN, Infinity
995     /**
996      * @j2sNative
997      * 
998      * if (sf.indexOf(".") < 0 && sf.indexOf("e") < 0 && sf.indexOf("N") < 0 && sf.indexOf("n") < 0)
999      *   sf += ".0";
1000      */
1001     {
1002     }
1003     return sf;
1004   }
1005   public static String join(String[] s, char c, int i0) {
1006     if (s.length < i0)
1007       return null;
1008     SB sb = new SB();
1009     sb.append(s[i0++]);
1010     for (int i = i0; i < s.length; i++)
1011       sb.appendC(c).append(s[i]);
1012     return sb.toString();
1013   }
1014
1015   /**
1016    * a LIKE "x"    a is a string and equals x
1017    * 
1018    * a LIKE "*x"   a is a string and ends with x
1019    * 
1020    * a LIKE "x*"   a is a string and starts with x
1021    * 
1022    * a LIKE "*x*"  a is a string and contains x
1023    *  
1024    * @param a
1025    * @param b
1026    * @return  a LIKE b
1027    */
1028   public static boolean isLike(String a, String b) {
1029     boolean areEqual = a.equals(b);
1030     if (areEqual)
1031       return true;
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));
1038   }
1039
1040   public static Object getMapValueNoCase(Map<String, ?> h, String key) {
1041     if ("this".equals(key))
1042       return h;
1043     Object val = h.get(key);
1044     if (val == null)
1045       for (Entry<String, ?> e : h.entrySet())
1046         if (e.getKey().equalsIgnoreCase(key))
1047           return e.getValue();
1048     return val;
1049   }
1050
1051   public static String clean(String s) {
1052     return rep(replaceAllCharacters(s, " \t\n\r", " "), "  ", " ").trim();
1053   }
1054
1055   /**
1056    * 
1057    * fdup      duplicates p or q formats for formatCheck
1058    *           and the format() function.
1059    * 
1060    * @param f
1061    * @param pt
1062    * @param n
1063    * @return     %3.5q%3.5q%3.5q%3.5q or %3.5p%3.5p%3.5p
1064    */
1065   public static String fdup(String f, int pt, int n) {
1066     char ch;
1067     int count = 0;
1068     for (int i = pt; --i >= 1; ) {
1069       if (isDigit(ch = f.charAt(i)))
1070         continue;
1071       switch (ch) {
1072       case '.':
1073         if (count++ != 0)
1074           return f;
1075         continue;
1076       case '-':
1077         if (i != 1 && f.charAt(i - 1) != '.')
1078           return f;
1079         continue;
1080       default:
1081         return f;
1082       }
1083     }
1084     String s = f.substring(0, pt + 1);
1085     SB sb = new SB();
1086     for (int i = 0; i < n; i++)
1087       sb.append(s);
1088     sb.append(f.substring(pt + 1));
1089     return sb.toString();
1090   }
1091
1092   /**
1093    * generic string formatter  based on formatLabel in Atom
1094    * 
1095    * 
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
1103    */
1104   
1105   private static String formatString(String strFormat, String key, String strT,
1106                                     float floatT, double doubleT, boolean doOne) {
1107     if (strFormat == null)
1108       return null;
1109     if ("".equals(strFormat))
1110       return "";
1111     int len = key.length();
1112     if (strFormat.indexOf("%") < 0 || len == 0 || strFormat.indexOf(key) < 0)
1113       return strFormat;
1114   
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) {
1123         strLabel += '%';
1124         continue;//%12.10x
1125       }
1126       try {
1127         boolean alignLeft = false;
1128         if (strFormat.charAt(ich) == '-') {
1129           alignLeft = true;
1130           ++ich;
1131         }
1132         boolean zeroPad = false;
1133         if (strFormat.charAt(ich) == '0') {
1134           zeroPad = true;
1135           ++ich;
1136         }
1137         char ch;
1138         int width = 0;
1139         while ((ch = strFormat.charAt(ich)) >= '0' && (ch <= '9')) {
1140           width = (10 * width) + (ch - '0');
1141           ++ich;
1142         }
1143         int precision = Integer.MAX_VALUE;
1144         boolean isExponential = false;
1145         if (strFormat.charAt(ich) == '.') {
1146           ++ich;
1147           if ((ch = strFormat.charAt(ich)) == '-') {
1148             isExponential = (strT == null);
1149             ++ich;
1150           } 
1151           if ((ch = strFormat.charAt(ich)) >= '0' && ch <= '9') {
1152             precision = ch - '0';
1153             ++ich;
1154           }
1155           if (isExponential)
1156             precision = -precision;
1157         }
1158         String st = strFormat.substring(ich, ich + len);
1159         if (!st.equals(key)) {
1160           ich = ichPercent + 1;
1161           strLabel += '%';
1162           continue;
1163         }
1164         ich += len;
1165         if (!Float.isNaN(floatT)) // 'f'
1166           strLabel += formatF(floatT, width, precision, alignLeft,
1167               zeroPad);
1168         else if (strT != null)  // 'd' 'i' or 's'
1169           strLabel += formatS(strT, width, precision, alignLeft,
1170               zeroPad);
1171         else if (!Double.isNaN(doubleT)) // 'e'
1172           strLabel += formatD(doubleT, width, precision - 1, alignLeft,
1173               zeroPad, true);
1174         if (doOne)
1175           break;
1176       } catch (IndexOutOfBoundsException ioobe) {
1177         ich = ichPercent;
1178         break;
1179       }
1180     }
1181     strLabel += strFormat.substring(ich);
1182     //if (strLabel.length() == 0)
1183       //return null;
1184     return strLabel;
1185   }
1186
1187   public static String formatStringS(String strFormat, String key, String strT) {
1188     return formatString(strFormat, key, strT, Float.NaN, Double.NaN, false);
1189   }
1190
1191   public static String formatStringF(String strFormat, String key, float floatT) {
1192     return formatString(strFormat, key, null, floatT, Double.NaN, false);
1193   }
1194
1195   public static String formatStringI(String strFormat, String key, int intT) {
1196     return formatString(strFormat, key, "" + intT, Float.NaN, Double.NaN, false);
1197   }
1198
1199   /**
1200    * sprintf emulation uses (almost) c++ standard string formats
1201    * 
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" --
1204    * digits?)
1205    * 
1206    * @param strFormat
1207    * @param list
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[]
1211    * @param values
1212    *        Object[] containing above types
1213    * @return formatted string
1214    */
1215   public static String sprintf(String strFormat, String list, Object[] values) {
1216     if (values == null)
1217       return strFormat;
1218     int n = list.length();
1219     if (n == values.length)
1220       try {
1221         for (int o = 0; o < n; o++) {
1222           if (values[o] == null)
1223             continue;
1224           switch (list.charAt(o)) {
1225           case 's':
1226             strFormat = formatString(strFormat, "s", (String) values[o],
1227                 Float.NaN, Double.NaN, true);
1228             break;
1229           case 'f':
1230             strFormat = formatString(strFormat, "f", null, ((Float) values[o])
1231                 .floatValue(), Double.NaN, true);
1232             break;
1233           case 'i':
1234             strFormat = formatString(strFormat, "d", "" + values[o], Float.NaN,
1235                 Double.NaN, true);
1236             strFormat = formatString(strFormat, "i", "" + values[o], Float.NaN,
1237                 Double.NaN, true);
1238             break;
1239           case 'd':
1240             strFormat = formatString(strFormat, "e", null, Float.NaN,
1241                 ((Double) values[o]).doubleValue(), true);
1242             break;
1243           case 'p':
1244             T3 pVal = (T3) values[o];
1245             strFormat = formatString(strFormat, "p", null, pVal.x, Double.NaN,
1246                 true);
1247             strFormat = formatString(strFormat, "p", null, pVal.y, Double.NaN,
1248                 true);
1249             strFormat = formatString(strFormat, "p", null, pVal.z, Double.NaN,
1250                 true);
1251             break;
1252           case 'q':
1253             T4 qVal = (T4) values[o];
1254             strFormat = formatString(strFormat, "q", null, qVal.x, Double.NaN,
1255                 true);
1256             strFormat = formatString(strFormat, "q", null, qVal.y, Double.NaN,
1257                 true);
1258             strFormat = formatString(strFormat, "q", null, qVal.z, Double.NaN,
1259                 true);
1260             strFormat = formatString(strFormat, "q", null, qVal.w, Double.NaN,
1261                 true);
1262             break;
1263           case 'S':
1264             String[] sVal = (String[]) values[o];
1265             for (int i = 0; i < sVal.length; i++)
1266               strFormat = formatString(strFormat, "s", sVal[i], Float.NaN,
1267                   Double.NaN, true);
1268             break;
1269           case 'F':
1270             float[] fVal = (float[]) values[o];
1271             for (int i = 0; i < fVal.length; i++)
1272               strFormat = formatString(strFormat, "f", null, fVal[i],
1273                   Double.NaN, true);
1274             break;
1275           case '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,
1279                   Double.NaN, true);
1280             for (int i = 0; i < iVal.length; i++)
1281               strFormat = formatString(strFormat, "i", "" + iVal[i], Float.NaN,
1282                   Double.NaN, true);
1283             break;
1284           case 'D':
1285             double[] dVal = (double[]) values[o];
1286             for (int i = 0; i < dVal.length; i++)
1287               strFormat = formatString(strFormat, "e", null, Float.NaN,
1288                   dVal[i], true);
1289           }
1290   
1291         }
1292         return rep(strFormat, "%%", "%");
1293       } catch (Exception e) {
1294         //
1295       }
1296     System.out.println("TextFormat.sprintf error " + list + " " + strFormat);
1297     return rep(strFormat, "%", "?");
1298   }
1299
1300   /**
1301    * 
1302    * formatCheck   checks p and q formats and duplicates if necessary
1303    *               "%10.5p xxxx" ==> "%10.5p%10.5p%10.5p xxxx" 
1304    * 
1305    * @param strFormat
1306    * @return    f or dupicated format
1307    */
1308   public static String formatCheck(String strFormat) {
1309     if (strFormat == null || strFormat.indexOf('p') < 0 && strFormat.indexOf('q') < 0)
1310       return strFormat;
1311     strFormat = rep(strFormat, "%%", "\1");
1312     strFormat = rep(strFormat, "%p", "%6.2p");
1313     strFormat = rep(strFormat, "%q", "%6.2q");
1314     String[] format = split(strFormat, "%");
1315     SB sb = new SB();
1316     sb.append(format[0]);
1317     for (int i = 1; i < format.length; i++) {
1318       String f = "%" + format[i];
1319       int pt;
1320       if (f.length() >= 3) {
1321         if ((pt = f.indexOf('p')) >= 0)
1322           f = fdup(f, pt, 3);
1323         if ((pt = f.indexOf('q')) >= 0)
1324           f = fdup(f, pt, 4);
1325       }
1326       sb.append(f);
1327     }
1328     return sb.toString().replace('\1', '%');
1329   }
1330
1331   public static void leftJustify(SB s, String s1, String s2) {
1332     s.append(s2);
1333     int n = s1.length() - s2.length();
1334     if (n > 0)
1335       s.append(s1.substring(0, n));
1336   }
1337
1338   public static void rightJustify(SB s, String s1, String s2) {
1339     int n = s1.length() - s2.length();
1340     if (n > 0)
1341       s.append(s1.substring(0, n));
1342     s.append(s2);
1343   }
1344
1345   public static String safeTruncate(float f, int n) {
1346     if (f > -0.001 && f < 0.001)
1347       f = 0;
1348     return (f + "         ").substring(0,n);
1349   }
1350
1351   public static boolean isWild(String s) {
1352     return s != null && (s.indexOf("*") >= 0 || s.indexOf("?") >= 0);
1353   }
1354
1355   /**
1356    * A general non-regex (for performance) text matcher that utilizes ? and *.
1357    * 
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 ?
1361    * 
1362    * @param search
1363    *        the string to search
1364    * @param match
1365    *        the match string
1366    * @param checkStar
1367    * @param allowInitialStar
1368    * @return true if found
1369    */
1370   public static boolean isMatch(String search, String match, boolean checkStar,
1371                                 boolean allowInitialStar) {
1372     // search == match --> true
1373     if (search.equals(match))
1374       return true;
1375     int mLen = match.length();
1376     // match == ""  --> false
1377     if (mLen == 0)
1378       return false;
1379     boolean isStar0 = (checkStar && allowInitialStar ? match.charAt(0) == '*'
1380         : false);
1381     // match == "*" --> true
1382     if (mLen == 1 && isStar0)
1383       return true;
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"
1390     if (!haveQ) {
1391       if (isStar0)
1392         return (isStar1 ? (mLen < 3 || search.indexOf(match.substring(1,
1393             mLen - 1)) >= 0) : search.endsWith(match.substring(1)));
1394       else if (isStar1)
1395         return search.startsWith(match.substring(0, mLen - 1));
1396     }
1397     int sLen = search.length();
1398     // pad match with "?" -- same as *
1399     String qqqq = "????";
1400     int nq = 4;
1401     while (nq < sLen) {
1402       qqqq += qqqq;
1403       nq += 4;
1404     }
1405     if (checkStar) {
1406       if (isStar0) {
1407         match = qqqq + match.substring(1);
1408         mLen += nq - 1;
1409       }
1410       if (isStar1) {
1411         match = match.substring(0, mLen - 1) + qqqq;
1412         mLen += nq - 1;
1413       }
1414     }
1415     // length of match < length of search --> false 
1416     if (mLen < sLen)
1417       return false;
1418   
1419     // -- each ? matches ONE character if not at end
1420     // -- extra ? at end ignored
1421   
1422     // (allowInitialStar == true)
1423     // -- extra ? at beginning reduced to match length
1424   
1425     int ich = 0;
1426     while (mLen > sLen) {
1427       if (allowInitialStar && match.charAt(ich) == '?') {
1428         ++ich;
1429       } else if (match.charAt(ich + mLen - 1) != '?') {
1430         return false;
1431       }
1432       --mLen;
1433     }
1434   
1435     // both are effectively same length now.
1436     // \1 is stand-in for "?"
1437   
1438     for (int i = sLen; --i >= 0;) {
1439       char chm = match.charAt(ich + i);
1440       if (chm == '?')
1441         continue;
1442       char chs = search.charAt(i);
1443       if (chm != chs && (chm != '\1' || chs != '?'))
1444         return false;
1445     }
1446     return true;
1447   }
1448
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
1457             + "\"");
1458     }
1459     return s;
1460   }
1461
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);
1470     }
1471     return s;
1472   }
1473
1474   public static boolean isDigit(char ch) {
1475     // just way simpler code than  Character.isDigit(ch);
1476     int c = ch;
1477     return (48 <= c && c <= 57);
1478   }
1479
1480   public static boolean isUpperCase(char ch) {
1481     int c = ch;
1482     return (65 <= c && c <= 90);
1483   }
1484
1485   public static boolean isLowerCase(char ch) {
1486     int c = ch;
1487     return (97 <= c && c <= 122);
1488   }
1489
1490   public static boolean isLetter(char ch) {
1491     // just way simpler code than     Character.isLetter(ch);
1492     int c = ch;
1493     return (65 <= c && c <= 90 || 97 <= c && c <= 122);
1494   }
1495
1496   public static boolean isLetterOrDigit(char ch) {
1497     // just way simpler code than     Character.isLetterOrDigit(ch);
1498     int c = ch;
1499     return (65 <= c && c <= 90 || 97 <= c && c <= 122 || 48 <= c && c <= 57);
1500   }
1501
1502   public static boolean isWhitespace(char ch) {
1503     int c = ch;
1504     return (c >= 0x1c && c <= 0x20 || c >= 0x9 && c <= 0xd);
1505   }
1506
1507   public static final float FRACTIONAL_PRECISION = 100000f;
1508   public static final float CARTESIAN_PRECISION =  10000f;
1509
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;
1515   }
1516   
1517   public static double fixDouble(double d, double f) {
1518     return Math.round(d * f) / f;
1519   }
1520
1521   /**
1522    * parse a float or "float/float"
1523    * @param s
1524    * @return a/b
1525    */
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)));
1530   }
1531
1532 //static {
1533 //    
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.);
1542 //  x+=1; 
1543 //  d+=1;
1544 //  }
1545 //  System.out.println(100.123456789f);
1546 //}
1547
1548 //  static {
1549 //    long t;
1550 //    char c = '0';
1551 //    t = System.currentTimeMillis();
1552 //    for (int i = 0; i < 10000000; i++) {
1553 //      boolean b = PT.isUpperCase(c);
1554 //    }
1555 //    System.out.println(System.currentTimeMillis() - t);
1556 //
1557 //    t = System.currentTimeMillis();
1558 //    for (int i = 0; i < 10000000; i++) {
1559 //      boolean b = Character.isUpperCase(c);
1560 //    }
1561 //    System.out.println(System.currentTimeMillis() - t);
1562 //    
1563 //    t = System.currentTimeMillis();
1564 //    for (int i = 0; i < 10000000; i++) {
1565 //      boolean b = PT.isUpperCase(c);
1566 //    }
1567 //    System.out.println(System.currentTimeMillis() - t);
1568 //
1569 //    System.out.println("PT test");
1570 //  }
1571 }