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