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