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