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 
parseInt(String str)44   public static int parseInt(String str) {
45     return parseIntNext(str, new int[] {0});
46   }
47 
parseIntNext(String str, int[] next)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 
parseIntChecked(String str, int ichMax, int[] next)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 
isWhiteSpace(String str, int ich)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    */
parseFloatChecked(String str, int ichMax, int[] next, boolean isStrict)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   };
checkTrailingText(String str, int ich, int ichMax)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 
parseFloatArray(String str)228   public static float[] parseFloatArray(String str) {
229     return parseFloatArrayNext(str, new int[1], null, null, null);
230   }
231 
parseFloatArrayInfested(String[] tokens, float[] data)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    */
parseFloatArrayNext(String str, int[] next, float[] f, String strStart, String strEnd)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 
parseFloatRange(String str, int ichMax, int[] next)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 
parseFloatNext(String str, int[] next)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 
parseFloatStrict(String str)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 
parseFloat(String str)310   public static float parseFloat(String str) {
311     return parseFloatNext(str, new int[] {0});
312   }
313 
parseIntRadix(String s, int i)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 
getTokens(String line)329   public static String[] getTokens(String line) {
330     return getTokensAt(line, 0);
331   }
332 
parseToken(String str)333   public static String parseToken(String str) {
334     return parseTokenNext(str, new int[] {0});
335   }
336 
parseTrimmed(String str)337   public static String parseTrimmed(String str) {
338     return parseTrimmedRange(str, 0, str.length());
339   }
340 
parseTrimmedAt(String str, int ichStart)341   public static String parseTrimmedAt(String str, int ichStart) {
342     return parseTrimmedRange(str, ichStart, str.length());
343   }
344 
parseTrimmedRange(String str, int ichStart, int ichMax)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 
getTokensAt(String line, int ich)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 
countChar(String line, char c)369   public static int countChar(String line, char c) {
370     int n = 0;
371     for (int i = line.lastIndexOf(c) + 1; --i >= 0;)
372       if (line.charAt(i) == c)
373         n++;
374     return n;
375   }
376 
countTokens(String line, int ich)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 
parseTokenNext(String str, int[] next)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 
parseTokenRange(String str, int ichMax, int[] next)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 
parseTokenChecked(String str, int ichMax, int[] next)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 
parseTrimmedChecked(String str, int ich, int ichMax)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 
dVal(String s)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 
fVal(String s)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 
parseIntRange(String str, int ichMax, int[] next)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    */
parseFloatArrayData(String[] tokens, float[] data)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    */
parseFloatArrayDataN(String[] tokens, float[] data, int nData)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    */
split(String text, String run)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 
getQuotedStringAt(String line, int ipt0)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    */
getQuotedStringNext(String line, int[] next)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    * single- or double-quoted string or up to the first space -- like HTML5
568    * not case-sensitive
569    *
570    * @param line
571    * @param key
572    * @return attribute
573    */
getQuotedOrUnquotedAttribute(String line, String key)574   public static String getQuotedOrUnquotedAttribute(String line, String key) {
575     if (line == null || key == null)
576       return null;
577     int pt = line.toLowerCase().indexOf(key.toLowerCase() + "=");
578     if (pt < 0 || (pt = pt + key.length() + 1) >= line.length())
579       return "";
580     char c = line.charAt(pt);
581     switch (c) {
582     case '\'':
583     case '"':
584       pt++;
585       break;
586     default:
587       c = ' ';
588       line += " ";
589     }
590     int pt1 = line.indexOf(c, pt);
591     return (pt1 < 0 ? null : line.substring(pt, pt1));
592   }
593 
594   /**
595    * CSV format -- escaped quote is "" WITHIN "..."
596    *
597    *
598    * @param line
599    * @param next int[2] filled with [ptrQuote1, ptrAfterQuote2]
600    *            next[1] will be -1 if unmatched quotes are found (continuation on next line)
601    * @return unescaped string or null
602    */
getCSVString(String line, int[] next)603   public static String getCSVString(String line, int[] next) {
604     int i = next[1];
605     if (i < 0 || (i = line.indexOf("\"", i)) < 0)
606       return null;
607     int pt = next[0] = i;
608     int len = line.length();
609     boolean escaped = false;
610     boolean haveEscape = false;
611     while (++i < len
612         && (line.charAt(i) != '"' || (escaped = (i + 1 < len && line.charAt(i + 1) == '"'))))
613       if (escaped) {
614         escaped = false;
615         haveEscape = true;
616         i++;
617       }
618     if (i >= len) {
619       next[1] = -1;
620       return null; // unmatched
621     }
622     next[1] = i + 1;
623     String s = line.substring(pt + 1, i);
624     return (haveEscape ? rep(rep(s, "\"\"", "\0"), "\0","\"") : s);
625   }
626 
isOneOf(String key, String semiList)627   public static boolean isOneOf(String key, String semiList) {
628     if (semiList.length() == 0)
629       return false;
630     if (semiList.charAt(0) != ';')
631       semiList = ";" + semiList + ";";
632     return key.indexOf(";") < 0  && semiList.indexOf(';' + key + ';') >= 0;
633   }
634 
getQuotedAttribute(String info, String name)635   public static String getQuotedAttribute(String info, String name) {
636     int i = info.indexOf(name + "=");
637     return (i < 0 ? null : getQuotedStringAt(info, i));
638   }
639 
approx(float f, float n)640   public static float approx(float f, float n) {
641     return Math.round (f * n) / n;
642   }
643 
644   /**
645    * Does a clean ITERATIVE replace of strFrom in str with strTo.
646    * Thus, rep("Testttt", "tt","t") becomes "Test".
647    *
648    * @param str
649    * @param strFrom
650    * @param strTo
651    * @return replaced string
652    */
rep(String str, String strFrom, String strTo)653   public static String rep(String str, String strFrom, String strTo) {
654     if (str == null || strFrom.length() == 0 || str.indexOf(strFrom) < 0)
655       return str;
656     boolean isOnce = (strTo.indexOf(strFrom) >= 0);
657     do {
658       str = str.replace(strFrom, strTo);
659     } while (!isOnce && str.indexOf(strFrom) >= 0);
660     return str;
661   }
662 
formatF(float value, int width, int precision, boolean alignLeft, boolean zeroPad)663   public static String formatF(float value, int width, int precision,
664                               boolean alignLeft, boolean zeroPad) {
665     return formatS(DF.formatDecimal(value, precision), width, 0, alignLeft, zeroPad);
666   }
667 
668   /**
669    *
670    * @param value
671    * @param width
672    * @param precision
673    * @param alignLeft
674    * @param zeroPad
675    * @param allowOverflow IGNORED
676    * @return formatted string
677    */
formatD(double value, int width, int precision, boolean alignLeft, boolean zeroPad, boolean allowOverflow)678   public static String formatD(double value, int width, int precision,
679                               boolean alignLeft, boolean zeroPad, boolean allowOverflow) {
680     return formatS(DF.formatDecimal((float)value, -1 - precision), width, 0, alignLeft, zeroPad);
681   }
682 
683   /**
684    *
685    * @param value
686    * @param width       number of columns
687    * @param precision   precision > 0 ==> precision = number of characters max from left
688    *                    precision < 0 ==> -1 - precision = number of char. max from right
689    * @param alignLeft
690    * @param zeroPad     generally for numbers turned strings
691    * @return            formatted string
692    */
formatS(String value, int width, int precision, boolean alignLeft, boolean zeroPad)693   public static String formatS(String value, int width, int precision,
694                               boolean alignLeft, boolean zeroPad) {
695     if (value == null)
696       return "";
697     int len = value.length();
698     if (precision != Integer.MAX_VALUE && precision > 0
699         && precision < len)
700       value = value.substring(0, precision);
701     else if (precision < 0 && len + precision >= 0)
702       value = value.substring(len + precision + 1);
703 
704     int padLength = width - value.length();
705     if (padLength <= 0)
706       return value;
707     boolean isNeg = (zeroPad && !alignLeft && value.charAt(0) == '-');
708     char padChar = (zeroPad ? '0' : ' ');
709     char padChar0 = (isNeg ? '-' : padChar);
710 
711     SB sb = new SB();
712     if (alignLeft)
713       sb.append(value);
714     sb.appendC(padChar0);
715     for (int i = padLength; --i > 0;)
716       // this is correct, not >= 0
717       sb.appendC(padChar);
718     if (!alignLeft)
719       sb.append(isNeg ? padChar + value.substring(1) : value);
720     return sb.toString();
721   }
722 
723   /**
724    * Does a clean replace of any of the characters in str with chrTo
725    * If strTo contains strFrom, then only a single pass is done.
726    * Otherwise, multiple passes are made until no more replacements can be made.
727    *
728    * @param str
729    * @param strFrom
730    * @param chTo
731    * @return  replaced string
732    */
replaceWithCharacter(String str, String strFrom, char chTo)733   public static String replaceWithCharacter(String str, String strFrom,
734                                             char chTo) {
735     if (str == null)
736       return null;
737     for (int i = strFrom.length(); --i >= 0;)
738       str = str.replace(strFrom.charAt(i), chTo);
739     return str;
740   }
741 
742   /**
743    * Does a clean replace of any of the characters in str with strTo
744    * If strTo contains strFrom, then only a single pass is done.
745    * Otherwise, multiple passes are made until no more replacements can be made.
746    *
747    * @param str
748    * @param strFrom
749    * @param strTo
750    * @return  replaced string
751    */
replaceAllCharacters(String str, String strFrom, String strTo)752   public static String replaceAllCharacters(String str, String strFrom,
753                                             String strTo) {
754     for (int i = strFrom.length(); --i >= 0;) {
755       String chFrom = strFrom.substring(i, i + 1);
756       str = rep(str, chFrom, strTo);
757     }
758     return str;
759   }
760 
trim(String str, String chars)761   public static String trim(String str, String chars) {
762     if (str == null || str.length() == 0)
763       return str;
764     if (chars.length() == 0)
765       return str.trim();
766     int len = str.length();
767     int k = 0;
768     while (k < len && chars.indexOf(str.charAt(k)) >= 0)
769       k++;
770     int m = str.length() - 1;
771     while (m > k && chars.indexOf(str.charAt(m)) >= 0)
772       m--;
773     return str.substring(k, m + 1);
774   }
775 
trimQuotes(String value)776   public static String trimQuotes(String value) {
777     return (value != null && value.length() > 1 && value.startsWith("\"")
778         && value.endsWith("\"") ? value.substring(1, value.length() - 1)
779         : value);
780   }
781 
isNonStringPrimitive(Object info)782   public static boolean isNonStringPrimitive(Object info) {
783     // note that we don't use Double, Float, or Integer here
784     // because in JavaScript those would be false for unwrapped primitives
785     // coming from equivalent of Array.get()
786     // Strings will need their own escaped processing
787     /**
788      * @j2sNative
789      *
790      * if(typeof info == "number" || typeof info == "boolean") {
791      * return true;
792      * }
793      *
794      *
795      */
796     {}
797      return info instanceof Number || info instanceof Boolean;
798   }
799 
800   @SuppressWarnings({ "null", "unused", "unchecked" })
toJSON(String infoType, Object info)801   public static String toJSON(String infoType, Object info) {
802     if (info == null)
803       return packageJSON(infoType, null);
804     if (isNonStringPrimitive(info))
805       return packageJSON(infoType, info.toString());
806     String s = null;
807     SB sb = null;
808     while (true) {
809       if (info instanceof String) {
810         s = (String) info;
811         /**
812          * @j2sNative
813          *
814          *            if (typeof s == "undefined") s = "null"
815          *
816          */
817         {
818         }
819 
820         if (s.indexOf("{\"") != 0) {
821           //don't doubly fix JSON strings when retrieving status
822           // what about  \1 \2 \3 etc.?
823           s = esc(s);
824         }
825         break;
826       }
827       if (info instanceof JSONEncodable) {
828         // includes javajs.util.BS, org.jmol.script.SV
829         if ((s = ((JSONEncodable) info).toJSON()) == null)
830           s = "null"; // perhaps a list has a null value (group3List, for example)
831         break;
832       }
833       sb = new SB();
834       if (info instanceof Map) {
835         sb.append("{ ");
836         String sep = "";
837         for (String key : ((Map<String, ?>) info).keySet()) {
838           if (key == null)
839             key = "null";
840           sb.append(sep).append(
841               packageJSON(key, toJSON(null, ((Map<?, ?>) info).get(key))));
842           sep = ",";
843         }
844         sb.append(" }");
845         break;
846       }
847       if (info instanceof Lst) {
848         sb.append("[ ");
849         int n = ((Lst<?>) info).size();
850         for (int i = 0; i < n; i++) {
851           if (i > 0)
852             sb.appendC(',');
853           sb.append(toJSON(null, ((Lst<?>) info).get(i)));
854         }
855         sb.append(" ]");
856         break;
857       }
858       if (info instanceof M34) {
859         // M4 extends M3
860         int len = (info instanceof M4 ? 4 : 3);
861         float[] x = new float[len];
862         M34 m = (M34) info;
863         sb.appendC('[');
864         for (int i = 0; i < len; i++) {
865           if (i > 0)
866             sb.appendC(',');
867           m.getRow(i, x);
868           sb.append(toJSON(null, x));
869         }
870         sb.appendC(']');
871         break;
872       }
873       s = nonArrayString(info);
874       if (s == null) {
875         sb.append("[");
876         int n = AU.getLength(info);
877         Object o = null;
878         /** @j2sNative
879          *  o = info[0];
880          *  typeof o != "number" && typeof 0 != "boolean" && (o = null);
881          */
882         {}
883         if (o != null) {
884           sb.appendO(info);
885         } else {
886           for (int i = 0; i < n; i++) {
887             if (i > 0)
888               sb.appendC(',');
889             sb.append(toJSON(null, Array.get(info, i)));
890           }
891         }
892         sb.append("]");
893         break;
894       }
895       info = info.toString();
896     }
897     return packageJSON(infoType, (s == null ? sb.toString() : s));
898   }
899 
900   /**
901    * Checks to see if an object is an array (including typed arrays), and if it is, returns null;
902    * otherwise it returns the string equivalent of that object.
903    *
904    * @param x
905    * @return String or null
906    */
nonArrayString(Object x)907   public static String nonArrayString(Object x) {
908     /**
909      * @j2sNative
910      *
911      * return (x.constructor == Array || x.BYTES_PER_ELEMENT ? null : x.toString());
912      *
913      */
914     {
915       try {
916         Array.getLength(x);
917         return null;
918       } catch (Exception e) {
919         return x.toString();
920       }
921     }
922   }
923 
byteArrayToJSON(byte[] data)924   public static String byteArrayToJSON(byte[] data) {
925     SB sb = new SB();
926     sb.append("[");
927     int n = data.length;
928     for (int i = 0; i < n; i++) {
929       if (i > 0)
930         sb.appendC(',');
931       sb.appendI(data[i] & 0xFF);
932     }
933     sb.append("]");
934     return sb.toString();
935   }
936 
packageJSON(String infoType, String info)937   public static String packageJSON(String infoType, String info) {
938     return (infoType == null ? info : "\"" + infoType + "\": " + info);
939   }
940 
escapeUrl(String url)941   public static String escapeUrl(String url) {
942     url = rep(url, "\n", "");
943     url = rep(url, "%", "%25");
944     url = rep(url, "#", "%23");
945     url = rep(url, "[", "%5B");
946     url = rep(url, "\\", "%5C");
947     url = rep(url, "]", "%5D");
948     url = rep(url, " ", "%20");
949     return url;
950   }
951 
952   private final static String escapable = "\\\\\tt\rr\nn\"\"";
953 
esc(String str)954   public static String esc(String str) {
955     if (str == null || str.length() == 0)
956       return "\"\"";
957     boolean haveEscape = false;
958     int i = 0;
959     for (; i < escapable.length(); i += 2)
960       if (str.indexOf(escapable.charAt(i)) >= 0) {
961         haveEscape = true;
962         break;
963       }
964     if (haveEscape)
965       while (i < escapable.length()) {
966         int pt = -1;
967         char ch = escapable.charAt(i++);
968         char ch2 = escapable.charAt(i++);
969         SB sb = new SB();
970         int pt0 = 0;
971         while ((pt = str.indexOf(ch, pt + 1)) >= 0) {
972           sb.append(str.substring(pt0, pt)).appendC('\\').appendC(ch2);
973           pt0 = pt + 1;
974         }
975         sb.append(str.substring(pt0, str.length()));
976         str = sb.toString();
977       }
978     return "\"" + escUnicode(str) + "\"";
979   }
980 
escUnicode(String str)981   public static String escUnicode(String str) {
982     for (int i = str.length(); --i >= 0;)
983       if (str.charAt(i) > 0x7F) {
984         String s = "0000" + Integer.toHexString(str.charAt(i));
985         str = str.substring(0, i) + "\\u" + s.substring(s.length() - 4)
986             + str.substring(i + 1);
987       }
988     return str;
989   }
990 
991   /**
992    * ensures that a float turned to string has a decimal point
993    *
994    * @param f
995    * @return string version of float
996    */
escF(float f)997   public static String escF(float f) {
998     String sf = "" + f;
999     /**
1000      * @j2sNative
1001      *
1002      * if (sf.indexOf(".") < 0 && sf.indexOf("e") < 0)
1003      *   sf += ".0";
1004      */
1005     {
1006     }
1007     return sf;
1008   }
join(String[] s, char c, int i0)1009   public static String join(String[] s, char c, int i0) {
1010     if (s.length < i0)
1011       return null;
1012     SB sb = new SB();
1013     sb.append(s[i0++]);
1014     for (int i = i0; i < s.length; i++)
1015       sb.appendC(c).append(s[i]);
1016     return sb.toString();
1017   }
1018 
1019   /**
1020    * a LIKE "x"    a is a string and equals x
1021    *
1022    * a LIKE "*x"   a is a string and ends with x
1023    *
1024    * a LIKE "x*"   a is a string and starts with x
1025    *
1026    * a LIKE "*x*"  a is a string and contains x
1027    *
1028    * @param a
1029    * @param b
1030    * @return  a LIKE b
1031    */
isLike(String a, String b)1032   public static boolean isLike(String a, String b) {
1033     boolean areEqual = a.equals(b);
1034     if (areEqual)
1035       return true;
1036     boolean isStart = b.startsWith("*");
1037     boolean isEnd = b.endsWith("*");
1038     return (!isStart && !isEnd) ? areEqual
1039         : isStart && isEnd ? b.length() == 1 || a.contains(b.substring(1, b.length() - 1))
1040         : isStart ? a.endsWith(b.substring(1))
1041         : a.startsWith(b.substring(0, b.length() - 1));
1042   }
1043 
getMapValueNoCase(Map<String, ?> h, String key)1044   public static Object getMapValueNoCase(Map<String, ?> h, String key) {
1045     if ("this".equals(key))
1046       return h;
1047     Object val = h.get(key);
1048     if (val == null)
1049       for (Entry<String, ?> e : h.entrySet())
1050         if (e.getKey().equalsIgnoreCase(key))
1051           return e.getValue();
1052     return val;
1053   }
1054 
clean(String s)1055   public static String clean(String s) {
1056     return rep(replaceAllCharacters(s, " \t\n\r", " "), "  ", " ").trim();
1057   }
1058 
1059   /**
1060    *
1061    * fdup      duplicates p or q formats for formatCheck
1062    *           and the format() function.
1063    *
1064    * @param f
1065    * @param pt
1066    * @param n
1067    * @return     %3.5q%3.5q%3.5q%3.5q or %3.5p%3.5p%3.5p
1068    */
fdup(String f, int pt, int n)1069   public static String fdup(String f, int pt, int n) {
1070     char ch;
1071     int count = 0;
1072     for (int i = pt; --i >= 1; ) {
1073       if (isDigit(ch = f.charAt(i)))
1074         continue;
1075       switch (ch) {
1076       case '.':
1077         if (count++ != 0)
1078           return f;
1079         continue;
1080       case '-':
1081         if (i != 1 && f.charAt(i - 1) != '.')
1082           return f;
1083         continue;
1084       default:
1085         return f;
1086       }
1087     }
1088     String s = f.substring(0, pt + 1);
1089     SB sb = new SB();
1090     for (int i = 0; i < n; i++)
1091       sb.append(s);
1092     sb.append(f.substring(pt + 1));
1093     return sb.toString();
1094   }
1095 
1096   /**
1097    * generic string formatter  based on formatLabel in Atom
1098    *
1099    *
1100    * @param strFormat   .... %width.precisionKEY....
1101    * @param key      any string to match
1102    * @param strT     replacement string or null
1103    * @param floatT   replacement float or Float.NaN
1104    * @param doubleT  replacement double or Double.NaN -- for exponential
1105    * @param doOne    mimic sprintf
1106    * @return         formatted string
1107    */
1108 
formatString(String strFormat, String key, String strT, float floatT, double doubleT, boolean doOne)1109   private static String formatString(String strFormat, String key, String strT,
1110                                     float floatT, double doubleT, boolean doOne) {
1111     if (strFormat == null)
1112       return null;
1113     if ("".equals(strFormat))
1114       return "";
1115     int len = key.length();
1116     if (strFormat.indexOf("%") < 0 || len == 0 || strFormat.indexOf(key) < 0)
1117       return strFormat;
1118 
1119     String strLabel = "";
1120     int ich, ichPercent, ichKey;
1121     for (ich = 0; (ichPercent = strFormat.indexOf('%', ich)) >= 0
1122         && (ichKey = strFormat.indexOf(key, ichPercent + 1)) >= 0;) {
1123       if (ich != ichPercent)
1124         strLabel += strFormat.substring(ich, ichPercent);
1125       ich = ichPercent + 1;
1126       if (ichKey > ichPercent + 6) {
1127         strLabel += '%';
1128         continue;//%12.10x
1129       }
1130       try {
1131         boolean alignLeft = false;
1132         if (strFormat.charAt(ich) == '-') {
1133           alignLeft = true;
1134           ++ich;
1135         }
1136         boolean zeroPad = false;
1137         if (strFormat.charAt(ich) == '0') {
1138           zeroPad = true;
1139           ++ich;
1140         }
1141         char ch;
1142         int width = 0;
1143         while ((ch = strFormat.charAt(ich)) >= '0' && (ch <= '9')) {
1144           width = (10 * width) + (ch - '0');
1145           ++ich;
1146         }
1147         int precision = Integer.MAX_VALUE;
1148         boolean isExponential = false;
1149         if (strFormat.charAt(ich) == '.') {
1150           ++ich;
1151           if ((ch = strFormat.charAt(ich)) == '-') {
1152             isExponential = (strT == null);
1153             ++ich;
1154           }
1155           if ((ch = strFormat.charAt(ich)) >= '0' && ch <= '9') {
1156             precision = ch - '0';
1157             ++ich;
1158           }
1159           if (isExponential)
1160             precision = -precision;
1161         }
1162         String st = strFormat.substring(ich, ich + len);
1163         if (!st.equals(key)) {
1164           ich = ichPercent + 1;
1165           strLabel += '%';
1166           continue;
1167         }
1168         ich += len;
1169         if (!Float.isNaN(floatT)) // 'f'
1170           strLabel += formatF(floatT, width, precision, alignLeft,
1171               zeroPad);
1172         else if (strT != null)  // 'd' 'i' or 's'
1173           strLabel += formatS(strT, width, precision, alignLeft,
1174               zeroPad);
1175         else if (!Double.isNaN(doubleT)) // 'e'
1176           strLabel += formatD(doubleT, width, precision - 1, alignLeft,
1177               zeroPad, true);
1178         if (doOne)
1179           break;
1180       } catch (IndexOutOfBoundsException ioobe) {
1181         ich = ichPercent;
1182         break;
1183       }
1184     }
1185     strLabel += strFormat.substring(ich);
1186     //if (strLabel.length() == 0)
1187       //return null;
1188     return strLabel;
1189   }
1190 
formatStringS(String strFormat, String key, String strT)1191   public static String formatStringS(String strFormat, String key, String strT) {
1192     return formatString(strFormat, key, strT, Float.NaN, Double.NaN, false);
1193   }
1194 
formatStringF(String strFormat, String key, float floatT)1195   public static String formatStringF(String strFormat, String key, float floatT) {
1196     return formatString(strFormat, key, null, floatT, Double.NaN, false);
1197   }
1198 
formatStringI(String strFormat, String key, int intT)1199   public static String formatStringI(String strFormat, String key, int intT) {
1200     return formatString(strFormat, key, "" + intT, Float.NaN, Double.NaN, false);
1201   }
1202 
1203   /**
1204    * sprintf emulation uses (almost) c++ standard string formats
1205    *
1206    * 's' string 'i' or 'd' integer, 'e' double, 'f' float, 'p' point3f 'q'
1207    * quaternion/plane/axisangle with added "i" (equal to the insipid "d" --
1208    * digits?)
1209    *
1210    * @param strFormat
1211    * @param list
1212    *        a listing of what sort of data will be found in Object[] values, in
1213    *        order: s string, f float, i integer, d double, p point3f, q
1214    *        quaternion/point4f, S String[], F float[], I int[], and D double[]
1215    * @param values
1216    *        Object[] containing above types
1217    * @return formatted string
1218    */
sprintf(String strFormat, String list, Object[] values)1219   public static String sprintf(String strFormat, String list, Object[] values) {
1220     if (values == null)
1221       return strFormat;
1222     int n = list.length();
1223     if (n == values.length)
1224       try {
1225         for (int o = 0; o < n; o++) {
1226           if (values[o] == null)
1227             continue;
1228           switch (list.charAt(o)) {
1229           case 's':
1230             strFormat = formatString(strFormat, "s", (String) values[o],
1231                 Float.NaN, Double.NaN, true);
1232             break;
1233           case 'f':
1234             strFormat = formatString(strFormat, "f", null, ((Float) values[o])
1235                 .floatValue(), Double.NaN, true);
1236             break;
1237           case 'i':
1238             strFormat = formatString(strFormat, "d", "" + values[o], Float.NaN,
1239                 Double.NaN, true);
1240             strFormat = formatString(strFormat, "i", "" + values[o], Float.NaN,
1241                 Double.NaN, true);
1242             break;
1243           case 'd':
1244             strFormat = formatString(strFormat, "e", null, Float.NaN,
1245                 ((Double) values[o]).doubleValue(), true);
1246             break;
1247           case 'p':
1248             T3 pVal = (T3) values[o];
1249             strFormat = formatString(strFormat, "p", null, pVal.x, Double.NaN,
1250                 true);
1251             strFormat = formatString(strFormat, "p", null, pVal.y, Double.NaN,
1252                 true);
1253             strFormat = formatString(strFormat, "p", null, pVal.z, Double.NaN,
1254                 true);
1255             break;
1256           case 'q':
1257             T4 qVal = (T4) values[o];
1258             strFormat = formatString(strFormat, "q", null, qVal.x, Double.NaN,
1259                 true);
1260             strFormat = formatString(strFormat, "q", null, qVal.y, Double.NaN,
1261                 true);
1262             strFormat = formatString(strFormat, "q", null, qVal.z, Double.NaN,
1263                 true);
1264             strFormat = formatString(strFormat, "q", null, qVal.w, Double.NaN,
1265                 true);
1266             break;
1267           case 'S':
1268             String[] sVal = (String[]) values[o];
1269             for (int i = 0; i < sVal.length; i++)
1270               strFormat = formatString(strFormat, "s", sVal[i], Float.NaN,
1271                   Double.NaN, true);
1272             break;
1273           case 'F':
1274             float[] fVal = (float[]) values[o];
1275             for (int i = 0; i < fVal.length; i++)
1276               strFormat = formatString(strFormat, "f", null, fVal[i],
1277                   Double.NaN, true);
1278             break;
1279           case 'I':
1280             int[] iVal = (int[]) values[o];
1281             for (int i = 0; i < iVal.length; i++)
1282               strFormat = formatString(strFormat, "d", "" + iVal[i], Float.NaN,
1283                   Double.NaN, true);
1284             for (int i = 0; i < iVal.length; i++)
1285               strFormat = formatString(strFormat, "i", "" + iVal[i], Float.NaN,
1286                   Double.NaN, true);
1287             break;
1288           case 'D':
1289             double[] dVal = (double[]) values[o];
1290             for (int i = 0; i < dVal.length; i++)
1291               strFormat = formatString(strFormat, "e", null, Float.NaN,
1292                   dVal[i], true);
1293           }
1294 
1295         }
1296         return rep(strFormat, "%%", "%");
1297       } catch (Exception e) {
1298         //
1299       }
1300     System.out.println("TextFormat.sprintf error " + list + " " + strFormat);
1301     return rep(strFormat, "%", "?");
1302   }
1303 
1304   /**
1305    *
1306    * formatCheck   checks p and q formats and duplicates if necessary
1307    *               "%10.5p xxxx" ==> "%10.5p%10.5p%10.5p xxxx"
1308    *
1309    * @param strFormat
1310    * @return    f or dupicated format
1311    */
formatCheck(String strFormat)1312   public static String formatCheck(String strFormat) {
1313     if (strFormat == null || strFormat.indexOf('p') < 0 && strFormat.indexOf('q') < 0)
1314       return strFormat;
1315     strFormat = rep(strFormat, "%%", "\1");
1316     strFormat = rep(strFormat, "%p", "%6.2p");
1317     strFormat = rep(strFormat, "%q", "%6.2q");
1318     String[] format = split(strFormat, "%");
1319     SB sb = new SB();
1320     sb.append(format[0]);
1321     for (int i = 1; i < format.length; i++) {
1322       String f = "%" + format[i];
1323       int pt;
1324       if (f.length() >= 3) {
1325         if ((pt = f.indexOf('p')) >= 0)
1326           f = fdup(f, pt, 3);
1327         if ((pt = f.indexOf('q')) >= 0)
1328           f = fdup(f, pt, 4);
1329       }
1330       sb.append(f);
1331     }
1332     return sb.toString().replace('\1', '%');
1333   }
1334 
leftJustify(SB s, String s1, String s2)1335   public static void leftJustify(SB s, String s1, String s2) {
1336     s.append(s2);
1337     int n = s1.length() - s2.length();
1338     if (n > 0)
1339       s.append(s1.substring(0, n));
1340   }
1341 
rightJustify(SB s, String s1, String s2)1342   public static void rightJustify(SB s, String s1, String s2) {
1343     int n = s1.length() - s2.length();
1344     if (n > 0)
1345       s.append(s1.substring(0, n));
1346     s.append(s2);
1347   }
1348 
safeTruncate(float f, int n)1349   public static String safeTruncate(float f, int n) {
1350     if (f > -0.001 && f < 0.001)
1351       f = 0;
1352     return (f + "         ").substring(0,n);
1353   }
1354 
isWild(String s)1355   public static boolean isWild(String s) {
1356     return s != null && (s.indexOf("*") >= 0 || s.indexOf("?") >= 0);
1357   }
1358 
1359   /**
1360    * A general non-regex (for performance) text matcher that utilizes ? and *.
1361    *
1362    * ??? means "at most three" characters if at beginning or end;
1363    *   "exactly three" otherwise
1364    * \1 in search is a stand-in for actual ?
1365    *
1366    * @param search
1367    *        the string to search
1368    * @param match
1369    *        the match string
1370    * @param checkStar
1371    * @param allowInitialStar
1372    * @return true if found
1373    */
isMatch(String search, String match, boolean checkStar, boolean allowInitialStar)1374   public static boolean isMatch(String search, String match, boolean checkStar,
1375                                 boolean allowInitialStar) {
1376     // search == match --> true
1377     if (search.equals(match))
1378       return true;
1379     int mLen = match.length();
1380     // match == ""  --> false
1381     if (mLen == 0)
1382       return false;
1383     boolean isStar0 = (checkStar && allowInitialStar ? match.charAt(0) == '*'
1384         : false);
1385     // match == "*" --> true
1386     if (mLen == 1 && isStar0)
1387       return true;
1388     boolean isStar1 = (checkStar && match.endsWith("*"));
1389     boolean haveQ = (match.indexOf('?') >= 0);
1390     // match == "**" --> true
1391     // match == "*xxx*" --> search contains "xxx"
1392     // match == "*xxx" --> search ends with "xxx"
1393     // match == "xxx*" --> search starts with "xxx"
1394     if (!haveQ) {
1395       if (isStar0)
1396         return (isStar1 ? (mLen < 3 || search.indexOf(match.substring(1,
1397             mLen - 1)) >= 0) : search.endsWith(match.substring(1)));
1398       else if (isStar1)
1399         return search.startsWith(match.substring(0, mLen - 1));
1400     }
1401     int sLen = search.length();
1402     // pad match with "?" -- same as *
1403     String qqqq = "????";
1404     int nq = 4;
1405     while (nq < sLen) {
1406       qqqq += qqqq;
1407       nq += 4;
1408     }
1409     if (checkStar) {
1410       if (isStar0) {
1411         match = qqqq + match.substring(1);
1412         mLen += nq - 1;
1413       }
1414       if (isStar1) {
1415         match = match.substring(0, mLen - 1) + qqqq;
1416         mLen += nq - 1;
1417       }
1418     }
1419     // length of match < length of search --> false
1420     if (mLen < sLen)
1421       return false;
1422 
1423     // -- each ? matches ONE character if not at end
1424     // -- extra ? at end ignored
1425 
1426     // (allowInitialStar == true)
1427     // -- extra ? at beginning reduced to match length
1428 
1429     int ich = 0;
1430     while (mLen > sLen) {
1431       if (allowInitialStar && match.charAt(ich) == '?') {
1432         ++ich;
1433       } else if (match.charAt(ich + mLen - 1) != '?') {
1434         return false;
1435       }
1436       --mLen;
1437     }
1438 
1439     // both are effectively same length now.
1440     // \1 is stand-in for "?"
1441 
1442     for (int i = sLen; --i >= 0;) {
1443       char chm = match.charAt(ich + i);
1444       if (chm == '?')
1445         continue;
1446       char chs = search.charAt(i);
1447       if (chm != chs && (chm != '\1' || chs != '?'))
1448         return false;
1449     }
1450     return true;
1451   }
1452 
replaceQuotedStrings(String s, Lst<String> list, Lst<String> newList)1453   public static String replaceQuotedStrings(String s, Lst<String> list,
1454                                             Lst<String> newList) {
1455     int n = list.size();
1456     for (int i = 0; i < n; i++) {
1457       String name = list.get(i);
1458       String newName = newList.get(i);
1459       if (!newName.equals(name))
1460         s = rep(s, "\"" + name + "\"", "\"" + newName
1461             + "\"");
1462     }
1463     return s;
1464   }
1465 
replaceStrings(String s, Lst<String> list, Lst<String> newList)1466   public static String replaceStrings(String s, Lst<String> list,
1467                                       Lst<String> newList) {
1468     int n = list.size();
1469     for (int i = 0; i < n; i++) {
1470       String name = list.get(i);
1471       String newName = newList.get(i);
1472       if (!newName.equals(name))
1473         s = rep(s, name, newName);
1474     }
1475     return s;
1476   }
1477 
isDigit(char ch)1478   public static boolean isDigit(char ch) {
1479     // just way simpler code than  Character.isDigit(ch);
1480     int c = ch;
1481     return (48 <= c && c <= 57);
1482   }
1483 
isUpperCase(char ch)1484   public static boolean isUpperCase(char ch) {
1485     int c = ch;
1486     return (65 <= c && c <= 90);
1487   }
1488 
isLowerCase(char ch)1489   public static boolean isLowerCase(char ch) {
1490     int c = ch;
1491     return (97 <= c && c <= 122);
1492   }
1493 
isLetter(char ch)1494   public static boolean isLetter(char ch) {
1495     // just way simpler code than     Character.isLetter(ch);
1496     int c = ch;
1497     return (65 <= c && c <= 90 || 97 <= c && c <= 122);
1498   }
1499 
isLetterOrDigit(char ch)1500   public static boolean isLetterOrDigit(char ch) {
1501     // just way simpler code than     Character.isLetterOrDigit(ch);
1502     int c = ch;
1503     return (65 <= c && c <= 90 || 97 <= c && c <= 122 || 48 <= c && c <= 57);
1504   }
1505 
isWhitespace(char ch)1506   public static boolean isWhitespace(char ch) {
1507     int c = ch;
1508     return (c >= 0x1c && c <= 0x20 || c >= 0x9 && c <= 0xd);
1509   }
1510 
1511   public static final float FRACTIONAL_PRECISION = 100000f;
1512   public static final float CARTESIAN_PRECISION =  10000f;
1513 
fixPtFloats(T3 pt, float f)1514   public static void fixPtFloats(T3 pt, float f) {
1515     //this will equate float and double as long as -256 <= x <= 256
1516     pt.x = Math.round(pt.x * f) / f;
1517     pt.y = Math.round(pt.y * f) / f;
1518     pt.z = Math.round(pt.z * f) / f;
1519   }
1520 
fixDouble(double d, double f)1521   public static double fixDouble(double d, double f) {
1522     return Math.round(d * f) / f;
1523   }
1524 
1525   /**
1526    * parse a float or "float/float"
1527    * @param s
1528    * @return a/b
1529    */
parseFloatFraction(String s)1530   public static float parseFloatFraction(String s) {
1531       int pt = s.indexOf("/");
1532       return (pt < 0 ? parseFloat(s) : parseFloat(s.substring(0, pt))
1533           / parseFloat(s.substring(pt + 1)));
1534   }
1535 
1536 //static {
1537 //
1538 //  double d = 790.8999998888;
1539 //  float x  = 790.8999998888f;
1540 //  for (int i = 0; i < 50; i++) {
1541 //  System.out.println(x + " " + d);
1542 //  System.out.println(Math.round(x * 100000) / 100000f);
1543 //  System.out.println(Math.round(d * 100000) / 100000.);
1544 //  System.out.println(Math.round(x * 10000) / 10000f);
1545 //  System.out.println(Math.round(d * 10000) / 10000.);
1546 //  x+=1;
1547 //  d+=1;
1548 //  }
1549 //  System.out.println(100.123456789f);
1550 //}
1551 
1552 //  static {
1553 //    long t;
1554 //    char c = '0';
1555 //    t = System.currentTimeMillis();
1556 //    for (int i = 0; i < 10000000; i++) {
1557 //      boolean b = PT.isUpperCase(c);
1558 //    }
1559 //    System.out.println(System.currentTimeMillis() - t);
1560 //
1561 //    t = System.currentTimeMillis();
1562 //    for (int i = 0; i < 10000000; i++) {
1563 //      boolean b = Character.isUpperCase(c);
1564 //    }
1565 //    System.out.println(System.currentTimeMillis() - t);
1566 //
1567 //    t = System.currentTimeMillis();
1568 //    for (int i = 0; i < 10000000; i++) {
1569 //      boolean b = PT.isUpperCase(c);
1570 //    }
1571 //    System.out.println(System.currentTimeMillis() - t);
1572 //
1573 //    System.out.println("PT test");
1574 //  }
1575 }
1576