1 /* $RCSfile$
2  * $Author: hansonr $
3  * $Date: 2009-06-05 07:42:12 -0500 (Fri, 05 Jun 2009) $
4  * $Revision: 10958 $
5  *
6  * Copyright (C) 2003-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 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 org.jmol.script;
26 
27 import java.util.Arrays;
28 import java.util.Collections;
29 import java.util.Comparator;
30 import java.util.Hashtable;
31 import java.util.Map;
32 import java.util.Set;
33 import java.util.Map.Entry;
34 
35 
36 import javajs.util.BS;
37 import org.jmol.modelset.BondSet;
38 import org.jmol.util.BSUtil;
39 import org.jmol.util.Escape;
40 import org.jmol.viewer.Viewer;
41 
42 import javajs.api.JSONEncodable;
43 import javajs.util.Lst;
44 import javajs.util.SB;
45 
46 
47 import javajs.util.AU;
48 import javajs.util.BArray;
49 import javajs.util.Base64;
50 import javajs.util.M3;
51 import javajs.util.M34;
52 import javajs.util.M4;
53 import javajs.util.Measure;
54 import javajs.util.P3;
55 import javajs.util.P4;
56 import javajs.util.PT;
57 import javajs.util.Quat;
58 import javajs.util.T3;
59 
60 import javajs.util.V3;
61 
62 
63 /**
64  * ScriptVariable class
65  *
66  */
67 public class SV extends T implements JSONEncodable {
68 
69   public final static SV vT = newSV(on, 1, "true");
70   public final static SV vF = newSV(off, 0, "false");
71 
72   public int index = Integer.MAX_VALUE;
73 
74   public String myName;
75 
newV(int tok, Object value)76   public static SV newV(int tok, Object value) {
77     SV sv = new SV();
78     sv.tok = tok;
79     sv.value = value;
80     return sv;
81   }
82 
newI(int i)83   public static SV newI(int i) {
84     SV sv = new SV();
85     sv.tok = integer;
86     sv.intValue = i;
87     return sv;
88   }
89 
newF(float f)90   public static SV newF(float f) {
91     SV sv = new SV();
92     sv.tok = decimal;
93     sv.value = Float.valueOf(f);
94     return sv;
95   }
96 
newS(String s)97   public static SV newS(String s) {
98     return newV(string, s);
99   }
100 
newT(T x)101   public static SV newT(T x) {
102     return newSV(x.tok, x.intValue, x.value);
103   }
104 
newSV(int tok, int intValue, Object value)105   static SV newSV(int tok, int intValue, Object value) {
106     SV sv = newV(tok, value);
107     sv.intValue = intValue;
108     return sv;
109   }
110 
111   /**
112    *
113    * Creates a NEW version of the variable.
114    * Object values are not copied. (Just found no
115    * use for that.)
116    *
117    *
118    * @param v
119    * @return  new ScriptVariable
120    */
setv(SV v)121   SV setv(SV v) {
122     index = v.index;
123     intValue = v.intValue;
124     tok = v.tok;
125     value = v.value;
126     return this;
127   }
128 
129   @SuppressWarnings("unchecked")
sizeOf(T x)130   static int sizeOf(T x) {
131     switch (x == null ? nada : x.tok) {
132     case bitset:
133       return bsSelectToken(x).cardinality();
134     case on:
135     case off:
136       return -1;
137     case integer:
138       return -2;
139     case decimal:
140       return -4;
141     case point3f:
142       return -8;
143     case point4f:
144       return -16;
145     case matrix3f:
146       return -32;
147     case matrix4f:
148       return -64;
149     case barray:
150       return ((BArray) x.value).data.length;
151     case string:
152       return ((String) x.value).length();
153     case varray:
154       return x.intValue == Integer.MAX_VALUE ? ((SV)x).getList().size()
155           : sizeOf(selectItemTok(x, Integer.MIN_VALUE));
156     case hash:
157       return ((Map<String, SV>) x.value).size();
158     case context:
159       return ((ScriptContext) x.value).getFullMap().size();
160     default:
161       return 0;
162     }
163   }
164 
165   /**
166    * Must be updated if getVariable is updated!
167    *
168    * @param x
169    * @return if we recognize this as a variable
170    *
171    */
isVariableType(Object x)172   public static boolean isVariableType(Object x) {
173     return (x instanceof SV
174         || x instanceof Boolean
175         || x instanceof Integer
176         || x instanceof Float
177         || x instanceof String
178         || x instanceof T3    // stored as point3f
179         || x instanceof BS
180         || x instanceof P4    // stored as point4f
181         || x instanceof Quat // stored as point4f
182         || x instanceof M34
183         || x instanceof Map<?, ?>  // stored as Map<String, ScriptVariable>
184         || x instanceof Lst<?>
185         || x instanceof BArray
186         || x instanceof ScriptContext
187     // in JavaScript, all these will be "Array" which is fine;
188         || isArray(x)); // stored as list
189   }
190 
191   /**
192    * Must be updated if getVariable is updated!
193    *
194    * @param x
195    * @return if we recognize this as an primitive array type
196    *
197    */
isArray(Object x)198   private static boolean isArray(Object x) {
199     /**
200      * @j2sNative
201      *
202      *            return Clazz.instanceOf(x, Array);
203      */
204     {
205        return x instanceof SV[]
206            || x instanceof int[]
207            || x instanceof byte[]
208            || x instanceof float[]
209            || x instanceof double[]
210            || x instanceof String[]
211            || x instanceof T3[]
212            || x instanceof int[][]
213            || x instanceof float[][]
214            || x instanceof String[][]
215            || x instanceof double[][]
216            || x instanceof Float[]
217            || x instanceof Object[];
218     }
219   }
220 
221 
222   /**
223    * @param x
224    * @return a ScriptVariable of the input type, or if x is null, then a new
225    *         ScriptVariable, or, if the type is not found, a string version
226    */
227   @SuppressWarnings("unchecked")
getVariable(Object x)228   public static SV getVariable(Object x) {
229     if (x == null)
230       return newS("");
231     if (x instanceof SV)
232       return (SV) x;
233 
234     // the eight basic types are:
235     // boolean, integer, decimal, string, point3f, point4f, bitset, and list
236     // listf is a special temporary type for storing results
237     // of .all in preparation for .bin in the case of xxx.all.bin
238     // but with some work, this could be developed into a storage class
239 
240     if (x instanceof Boolean)
241       return getBoolean(((Boolean) x).booleanValue());
242     if (x instanceof Integer)
243       return newI(((Integer) x).intValue());
244     if (x instanceof Float)
245       return newV(decimal, x);
246     if (x instanceof String) {
247       x = unescapePointOrBitsetAsVariable(x);
248       if (x instanceof SV)
249         return (SV) x;
250       return newV(string, x);
251     }
252     if (x instanceof P3)
253       return newV(point3f, x);
254     if (x instanceof V3) // point3f is not mutable anyway
255       return newV(point3f, P3.newP((V3) x));
256     if (x instanceof BS)
257       return newV(bitset, x);
258     if (x instanceof P4)
259       return newV(point4f, x);
260     // note: for quaternions, we save them {q1, q2, q3, q0}
261     // While this may seem odd, it is so that for any point4 --
262     // planes, axisangles, and quaternions -- we can use the
263     // first three coordinates to determine the relavent axis
264     // the fourth then gives us offset to {0,0,0} (plane),
265     // rotation angle (axisangle), and cos(theta/2) (quaternion).
266     if (x instanceof Quat)
267       return newV(point4f, ((Quat) x).toPoint4f());
268     if (x instanceof M34)
269       return newV(x instanceof M4 ? matrix4f : matrix3f, x);
270     if (x instanceof Map)
271       return getVariableMap((Map<String, ?>)x);
272     if (x instanceof Lst)
273       return getVariableList((Lst<?>) x);
274     if (x instanceof BArray)
275       return newV(barray, x);
276     if (x instanceof ScriptContext)
277       return newV(context, x);
278     if (isASV(x))
279       return getVariableAV((SV[]) x);
280     // rest are specific array types supported
281     // DO NOT ADD MORE UNLESS YOU CHANGE isArray(), above
282     if (AU.isAI(x))
283       return getVariableAI((int[]) x);
284     if (AU.isAB(x))
285       return getVariableAB((byte[]) x);
286     if (AU.isAF(x))
287       return getVariableAF((float[]) x);
288     if (AU.isAD(x))
289       return getVariableAD((double[]) x);
290     if (AU.isAS(x))
291       return getVariableAS((String[]) x);
292     //NOTE: isAP(x) does not return TRUE for Atom[] in JavaScript
293     // only occurrence of this was for Polyhedron.getInfo()
294     if (AU.isAP(x))
295       return getVariableAP((T3[]) x);
296     if (AU.isAII(x))
297       return getVariableAII((int[][]) x);
298     if (AU.isAFF(x))
299       return getVariableAFF((float[][]) x);
300     if (AU.isASS(x))
301       return getVariableASS((String[][]) x);
302     if (AU.isADD(x))
303       return getVariableADD((double[][]) x);
304     if (AU.isAFloat(x))
305       return newV(listf, x);
306     return newJSVar(x);
307   }
308 
isASV(Object x)309   private static boolean isASV(Object x) {
310     if (!Viewer.isSwingJS) {
311   /**
312        * @j2sNative
313        *
314        * return x && x[0] && x[0].__CLASS_NAME__ == "JS.SV";
315        *
316        */
317       {}
318     }
319     return  x instanceof SV[];
320   }
321 
322   /**
323    * Conversion to Jmol variables of JavaScript variables using
324    * y = javascript("x")
325    *
326    * @param x a JavaScript variable, perhaps
327    * @return SV
328    */
329   @SuppressWarnings("unused")
newJSVar(Object x)330   private static SV newJSVar(Object x) {
331     // JavaScript only
332     int itype;
333     boolean itest;
334     float inum;
335     Object[] array;
336     String[] keys;
337     /**
338      * @j2sNative
339      *
340      *            switch(x.BYTES_PER_ELEMENT ? Array : x.constructor) {
341      *            case Boolean:
342      *              itype = 0;
343      *              itest = x;
344      *              break;
345      *            case Number:
346      *              itype = 1;
347      *              inum = x;
348      *              break;
349      *            case Array:
350      *              itype = 2;
351      *              array = x;
352      *              break;
353      *            case Object:
354      *               itype = 3;
355      *               array = x;
356      *               keys = Object.keys(x);
357      *               break;
358      *            }
359      *
360      */
361     {
362       if (x instanceof Object[])
363         return getVariableAO((Object[]) x);
364       if (true)
365         return newS(x.toString());
366     }
367 
368     // JavaScript only
369     switch (itype) {
370     case 0:
371       return (itest ? vT : vF);
372     case 1:
373       return (inum > Integer.MAX_VALUE || inum != Math.floor(inum)
374           ? SV.newF(inum) : newI((int) inum));
375     case 2:
376       Lst<SV> v = new javajs.util.Lst<SV>();
377       for (int i = 0, n = array.length; i < n; i++)
378         v.addLast(newJSVar(array[i]));
379       return getVariableList(v);
380     case 3:
381       Map<String, Object> map = new Hashtable<String, Object>();
382       for (int i = keys.length; --i >= 0;) {
383         Object o = null;
384         /**
385          * @j2sNative
386          *
387          *            o = array[keys[i]];
388          *
389          */
390         {
391         }
392         map.put(keys[i], newJSVar(o));
393       }
394       return SV.getVariableMap(map);
395     }
396     return newS(x.toString());
397   }
398 
399   @SuppressWarnings("unchecked")
getVariableMap(Map<String, ?> x)400   public static SV getVariableMap(Map<String, ?> x) {
401     Map<String, Object> ht = (Map<String, Object>) x;
402     Object o = null;
403     for (Object oo : ht.values()) {
404       o = oo;
405       break;
406     }
407     if (!(o instanceof SV)) {
408       Map<String, SV> x2 = new Hashtable<String, SV>();
409       for (Map.Entry<String, Object> entry : ht.entrySet())
410         x2.put(entry.getKey(), getVariable(entry.getValue()));
411       x = x2;
412     }
413     return newV(hash, x);
414   }
415 
getVariableList(Lst<?> v)416   public static SV getVariableList(Lst<?> v) {
417     int len = v.size();
418     if (len > 0 && v.get(0) instanceof SV)
419       return newV(varray, v);
420     Lst<SV> objects = new  Lst<SV>();
421     for (int i = 0; i < len; i++)
422       objects.addLast(getVariable(v.get(i)));
423     return newV(varray, objects);
424   }
425 
getVariableAV(SV[] v)426   static SV getVariableAV(SV[] v) {
427     Lst<SV> objects = new  Lst<SV>();
428     for (int i = 0; i < v.length; i++)
429       objects.addLast(v[i]);
430     return newV(varray, objects);
431   }
432 
getVariableAD(double[] f)433   public static SV getVariableAD(double[] f) {
434     Lst<SV> objects = new  Lst<SV>();
435     for (int i = 0; i < f.length; i++)
436       objects.addLast(newV(decimal, Float.valueOf((float) f[i])));
437     return newV(varray, objects);
438   }
439 
getVariableAO(Object[] o)440   static SV getVariableAO(Object[] o) {
441     Lst<SV> objects = new  Lst<SV>();
442     for (int i = 0; i < o.length; i++)
443       objects.addLast(getVariable(o[i]));
444     return newV(varray, objects);
445   }
446 
getVariableAS(String[] s)447   static SV getVariableAS(String[] s) {
448     Lst<SV> objects = new  Lst<SV>();
449     for (int i = 0; i < s.length; i++)
450       objects.addLast(newV(string, s[i]));
451     return newV(varray, objects);
452   }
453 
getVariableAP(T3[] p)454   static SV getVariableAP(T3[] p) {
455     Lst<SV> objects = new  Lst<SV>();
456     for (int i = 0; i < p.length; i++)
457       objects.addLast(newV(point3f, p[i]));
458     return newV(varray, objects);
459   }
460 
getVariableAFF(float[][] fx)461   static SV getVariableAFF(float[][] fx) {
462     Lst<SV> objects = new  Lst<SV>();
463     for (int i = 0; i < fx.length; i++)
464       objects.addLast(getVariableAF(fx[i]));
465     return newV(varray, objects);
466   }
467 
getVariableADD(double[][] fx)468   static SV getVariableADD(double[][] fx) {
469     Lst<SV> objects = new  Lst<SV>();
470     for (int i = 0; i < fx.length; i++)
471       objects.addLast(getVariableAD(fx[i]));
472     return newV(varray, objects);
473   }
474 
getVariableASS(String[][] fx)475   static SV getVariableASS(String[][] fx) {
476     Lst<SV> objects = new  Lst<SV>();
477     for (int i = 0; i < fx.length; i++)
478       objects.addLast(getVariableAS(fx[i]));
479     return newV(varray, objects);
480   }
481 
getVariableAII(int[][] ix)482   static SV getVariableAII(int[][] ix) {
483     Lst<SV> objects = new  Lst<SV>();
484     for (int i = 0; i < ix.length; i++)
485       objects.addLast(getVariableAI(ix[i]));
486     return newV(varray, objects);
487   }
488 
getVariableAF(float[] f)489   static SV getVariableAF(float[] f) {
490     Lst<SV> objects = new  Lst<SV>();
491     for (int i = 0; i < f.length; i++)
492       objects.addLast(newV(decimal, Float.valueOf(f[i])));
493     return newV(varray, objects);
494   }
495 
getVariableAI(int[] ix)496   static SV getVariableAI(int[] ix) {
497     Lst<SV> objects = new  Lst<SV>();
498     for (int i = 0; i < ix.length; i++)
499       objects.addLast(newI(ix[i]));
500     return newV(varray, objects);
501   }
502 
getVariableAB(byte[] ix)503   static SV getVariableAB(byte[] ix) {
504     Lst<SV> objects = new  Lst<SV>();
505     for (int i = 0; i < ix.length; i++)
506       objects.addLast(newI(ix[i]));
507     return newV(varray, objects);
508   }
509 
setName(String name)510   public SV setName(String name) {
511     this.myName = name;
512     //System.out.println("Variable: " + name + " " + intValue + " " + value);
513     return this;
514   }
515 
canIncrement()516   boolean canIncrement() {
517     switch (tok) {
518     case integer:
519     case decimal:
520       return true;
521     default:
522       return false;
523     }
524   }
525 
increment(int n)526   boolean increment(int n) {
527     switch (tok) {
528     case integer:
529       intValue += n;
530       return true;
531     case decimal:
532       value = Float.valueOf(((Float) value).floatValue() + n);
533       return true;
534     default:
535       return false;
536     }
537   }
538 
asBoolean()539   public boolean asBoolean() {
540     return bValue(this);
541   }
542 
asInt()543   public int asInt() {
544     return iValue(this);
545   }
546 
asFloat()547   public float asFloat() {
548     return fValue(this);
549   }
550 
asString()551   public String asString() {
552     return sValue(this);
553   }
554 
555   // math-related Token static methods
556 
557   private final static P3 pt0 = new P3();
558 
559   /**
560    *
561    * @param xx
562    * @return Object-wrapped value
563    */
564 
oValue(Object xx)565   public static Object oValue(Object xx) {
566     if (!(xx instanceof SV))
567       return xx;
568     SV x = (SV) xx;
569     switch (x.tok) {
570     case on:
571       return Boolean.TRUE;
572     case nada:
573     case off:
574       return Boolean.FALSE;
575     case integer:
576       return Integer.valueOf(x.intValue);
577     case bitset:
578     case array:
579       return selectItemVar(x).value; // TODO: matrix3f??
580     default:
581       return x.value;
582     }
583 
584   }
585 
586   /**
587    *
588    * @param x
589    * @return  numeric value -- integer or decimal
590    */
nValue(T x)591   static Object nValue(T x) {
592     int iValue;
593     switch (x == null ? nada : x.tok) {
594     case decimal:
595       return x.value;
596     case integer:
597       iValue = x.intValue;
598       break;
599     case string:
600       if (((String) x.value).indexOf(".") >= 0)
601         return Float.valueOf(toFloat((String) x.value));
602       iValue = (int) toFloat((String) x.value);
603       break;
604     case point3f:
605       return Float.valueOf(((T3) x.value).length());
606     default:
607       iValue = 0;
608     }
609     return Integer.valueOf(iValue);
610   }
611 
612   // there are reasons to use Token here rather than ScriptVariable
613   // for some of these functions, in particular iValue, fValue, and sValue
614 
bValue(T x)615   public static boolean bValue(T x) {
616     switch (x == null ? nada : x.tok) {
617     case on:
618     case context:
619       return true;
620     case off:
621       return false;
622     case integer:
623       return x.intValue != 0;
624     case decimal:
625     case string:
626     case varray:
627       return fValue(x) != 0;
628     case bitset:
629     case barray:
630       return iValue(x) != 0;
631     case point3f:
632     case point4f:
633     case matrix3f:
634     case matrix4f:
635       return Math.abs(fValue(x)) > 0.0001f;
636     case hash:
637       return !((SV) x).getMap().isEmpty();
638     default:
639       return false;
640     }
641   }
642 
iValue(T x)643   public static int iValue(T x) {
644     switch (x == null ? nada : x.tok) {
645     case on:
646       return 1;
647     case off:
648       return 0;
649     case integer:
650       return x.intValue;
651     case decimal:
652     case varray:
653     case string:
654     case point3f:
655     case point4f:
656     case matrix3f:
657     case matrix4f:
658     case quaternion:
659       return (int) fValue(x);
660     case bitset:
661       return bsSelectToken(x).cardinality();
662     case barray:
663       return ((BArray) x.value).data.length;
664     default:
665       return 0;
666     }
667   }
668 
fValue(T x)669   public static float fValue(T x) {
670     switch (x == null ? nada : x.tok) {
671     case on:
672       return 1;
673     case off:
674       return 0;
675     case integer:
676       return x.intValue;
677     case decimal:
678       return ((Float) x.value).floatValue();
679     case varray:
680       int i = x.intValue;
681       if (i == Integer.MAX_VALUE)
682         return ((SV)x).getList().size();
683       //$FALL-THROUGH$
684     case string:
685       return toFloat(sValue(x));
686     case bitset:
687     case barray:
688       return iValue(x);
689     case point3f:
690       return ((P3) x.value).length();
691     case point4f:
692       return Measure.distanceToPlane((P4) x.value, pt0);
693     case matrix3f:
694       P3 pt = new P3();
695       ((M3) x.value).rotate(pt);
696       return pt.length();
697     case matrix4f:
698       P3 pt1 = new P3();
699       ((M4) x.value).rotTrans(pt1);
700       return pt1.length();
701     default:
702       return 0;
703     }
704   }
705 
sValue(T x)706   public static String sValue(T x) {
707     if (x == null)
708       return "";
709     int i;
710     SB sb;
711     switch (x.tok) {
712     case on:
713       return "true";
714     case off:
715       return "false";
716     case integer:
717       return "" + x.intValue;
718     case bitset:
719       BS bs = bsSelectToken(x);
720       return (x.value instanceof BondSet ? Escape.eBond(bs) : Escape.eBS(bs));
721     case varray:
722       Lst<SV> sv = ((SV) x).getList();
723       i = x.intValue;
724       if (i <= 0)
725         i = sv.size() - i;
726       if (i != Integer.MAX_VALUE)
727         return (i < 1 || i > sv.size() ? "" : sValue(sv.get(i - 1)));
728       //$FALL-THROUGH$
729     case hash:
730     case context:
731       if (x.value instanceof String)
732         return (String) x.value; // just the command
733       sb = new SB();
734       sValueArray(sb, (SV) x, "", "", false, true, true, Integer.MAX_VALUE, false);
735       return PT.rep(sb.toString(), "\n\0", " "); // circular ref
736     case string:
737       String s = (String) x.value;
738       i = x.intValue;
739       if (i <= 0)
740         i = s.length() - i;
741       if (i == Integer.MAX_VALUE)
742         return s;
743       if (i < 1 || i > s.length())
744         return "";
745       return "" + s.charAt(i - 1);
746     case point3f:
747       return Escape.eP((T3) x.value);
748     case point4f:
749       return Escape.eP4((P4) x.value);
750     case matrix3f:
751     case matrix4f:
752       return Escape.e(x.value);
753     default:
754       return x.value.toString();
755     }
756   }
757 
sValueArray(SB sb, SV vx, String path, String tabs, boolean isEscaped, boolean isRaw, boolean addValues, int maxLevels, boolean skipEmpty)758   private static void sValueArray(SB sb, SV vx, String path, String tabs,
759                                   boolean isEscaped, boolean isRaw,
760                                   boolean addValues, int maxLevels,
761                                   boolean skipEmpty) {
762     switch (vx.tok) {
763     case hash:
764     case context:
765     case varray:
766       String thiskey = ";" + vx.hashCode() + ";";
767       if (path.indexOf(thiskey) >= 0) {
768         sb.append(isEscaped ? (vx.tok == varray ? "[ ]" : "{ }")
769             : (vx.tok == varray ? "" : "\0") + "\"<"
770             + (vx.myName == null ? "circular reference" : vx.myName) + ">\"");
771         break;
772       }
773       path += thiskey;
774       if (vx.tok == varray) {
775         if (!addValues)
776           return;
777         if (!isRaw)
778           sb.append(isEscaped ? "[ " : tabs + "[\n");
779         Lst<SV> sx = vx.getList();
780         for (int i = 0; i < sx.size(); i++) {
781           if (isEscaped && i > 0)
782             sb.append(",");
783           SV sv = sx.get(i);
784           sValueArray(sb, sv, path, tabs + "  ", isEscaped, tabs.length() == 0
785               && !isEscaped && isRawType(sv.tok), addValues, maxLevels,
786               skipEmpty);
787           if (!isEscaped)
788             sb.append("\n");
789         }
790         if (!isRaw)
791           sb.append(isEscaped ? " ]" : tabs + "]");
792       } else if (--maxLevels >= 0) {
793         Map<String, SV> ht = (vx.tok == context ? ((ScriptContext) vx.value)
794             .getFullMap() : vx.getMap());
795         sValueAddKeys(sb, path, ht, tabs, isEscaped, addValues, maxLevels, skipEmpty);
796       }
797       break;
798     default:
799       if (!addValues)
800         return;
801       if (!isRaw && !isEscaped)
802         sb.append(tabs);
803       sb.append(isEscaped ? vx.escape() : sValue(vx));
804     }
805   }
806 
807   @SuppressWarnings("cast")
sValueAddKeys(SB sb, String path, Map<String, SV> ht, String tabs, boolean isEscaped, boolean addValues, int maxLevels, boolean skipEmpty)808   private static void sValueAddKeys(SB sb, String path, Map<String, SV> ht, String tabs, boolean isEscaped, boolean addValues, int maxLevels, boolean skipEmpty) {
809     if (maxLevels < 0)
810       return;
811     Set<String> keyset = ht.keySet();
812     String[] keys = ht.keySet().toArray(new String[keyset.size()]);
813     Arrays.sort(keys);
814     if (isEscaped) {
815       sb.append("{ ");
816       String sep = "";
817       for (int i = 0; i < keys.length; i++) {
818         String key = keys[i];
819         SV val = ht.get(key);
820         if (skipEmpty && (val.tok == varray && val.getList().size() == 0
821             || val.tok == hash && val.getMap().isEmpty()))
822           continue;
823         if (addValues)
824           sb.append(sep).append(PT.esc(key)).append(":");
825         else
826            sb.appendC(' ').append(key);
827         sValueArray(sb, val, path, tabs+"  ", true, false, addValues, maxLevels, skipEmpty);
828         sep = ",";
829       }
830       sb.append(" }");
831       if (!addValues)
832         sb.append("\n");
833 
834       return;
835     }
836     sb.append(tabs).append("{\n");
837     tabs += "  ";
838     for (int i = 0; i < keys.length; i++) {
839       sb.append(tabs);
840       String key = keys[i];
841       sb.append(PT.esc(key)).append("  :");
842       SB sb2 = new SB();
843       if (!(ht.get(key) instanceof SV))
844         ht.put(key, SV.getVariable(ht.get(key)));
845       SV v = ht.get(key);
846       isEscaped = isRawType(v.tok);
847       sValueArray(sb2, v, path, tabs, isEscaped, false, addValues, maxLevels, skipEmpty);
848       String value = sb2.toString();
849       if (isEscaped && addValues)
850         sb.append("  ");
851       else
852         sb.append("\n");
853       sb.append(value).append("\n");
854     }
855     sb.append(tabs.substring(1)).append("}");
856   }
857 
isRawType(int tok)858   private static boolean isRawType(int tok) {
859     switch (tok) {
860     case string:
861     case decimal:
862     case integer:
863     case point3f:
864     case point4f:
865     case bitset:
866     case barray:
867     case on:
868     case off:
869       return true;
870     }
871     return false;
872   }
873 
ptValue(SV x)874   public static P3 ptValue(SV x) {
875     switch (x.tok) {
876     case point3f:
877       return (P3) x.value;
878     case string:
879       Object o = Escape.uP((String) x.value);
880       if (o instanceof P3)
881         return (P3) o;
882     }
883     return null;
884   }
885 
pt4Value(SV x)886   public static P4 pt4Value(SV x) {
887     switch (x.tok) {
888     case point4f:
889       return (P4) x.value;
890     case string:
891       Object o = Escape.uP((String) x.value);
892       if (!(o instanceof P4))
893         break;
894       return (P4) o;
895     }
896     return null;
897   }
898 
toFloat(String s)899   private static float toFloat(String s) {
900     return (s.equalsIgnoreCase("true") ? 1
901         : s.length() == 0 || s.equalsIgnoreCase("false") ? 0
902         : PT.parseFloatStrict(PT.trim(s," \t\n")));
903   }
904 
concatList(SV x1, SV x2, boolean asNew)905   public static SV concatList(SV x1, SV x2, boolean asNew) {
906     Lst<SV> v1 = x1.getList();
907     Lst<SV> v2 = x2.getList();
908     if (!asNew) {
909       if (v2 == null)
910         v1.addLast(newT(x2));
911       else
912         for (int i = 0; i < v2.size(); i++)
913           v1.addLast(v2.get(i));
914       return x1;
915     }
916     Lst<SV> vlist = new Lst<SV>();
917     //(v1 == null ? 1 : v1.size()) + (v2 == null ? 1 : v2.size())
918     if (v1 == null)
919       vlist.addLast(x1);
920     else
921       for (int i = 0; i < v1.size(); i++)
922         vlist.addLast(v1.get(i));
923     if (v2 == null)
924       vlist.addLast(x2);
925     else
926       for (int i = 0; i < v2.size(); i++)
927         vlist.addLast(v2.get(i));
928     return getVariableList(vlist);
929   }
930 
bsSelectToken(T x)931   private static BS bsSelectToken(T x) {
932     return (BS) selectItemTok(x, Integer.MIN_VALUE).value;
933   }
934 
bsSelectRange(T x, int n)935   static BS bsSelectRange(T x, int n) {
936     x = selectItemTok(x, Integer.MIN_VALUE);
937     x = selectItemTok(x, (n <= 0 ? n : 1));
938     x = selectItemTok(x, (n <= 0 ? Integer.MAX_VALUE - 1 : n));
939     return (BS) x.value;
940   }
941 
selectItemVar(SV var)942   static SV selectItemVar(SV var) {
943     // pass bitsets created by the select() or for() inline functions
944     // and all arrays by reference
945     return (var.index != Integer.MAX_VALUE
946         || (var.tok == varray || var.tok == barray)
947         && var.intValue == Integer.MAX_VALUE ? var : (SV) selectItemTok(var,
948         Integer.MIN_VALUE));
949   }
950 
selectItemTok(T tokenIn, int i2)951   static T selectItemTok(T tokenIn, int i2) {
952     switch (tokenIn.tok) {
953     case matrix3f:
954     case matrix4f:
955     case bitset:
956     case varray:
957     case barray:
958     case string:
959       break;
960     default:
961       return ((tokenIn instanceof SV) && ((SV) tokenIn).myName != null
962       ? newI(0).setv((SV) tokenIn)
963           : tokenIn);
964     }
965 
966     // negative number is a count from the end
967 
968     BS bs = null;
969     String s = null;
970 
971     int i1 = tokenIn.intValue;
972     boolean isOne = (i2 == Integer.MIN_VALUE);
973     if (i1 == Integer.MAX_VALUE) {
974       // no selections have been made yet --
975       // we just create a new token with the
976       // same bitset and now indicate either
977       // the selected value or "ALL" (max_value)
978       return newSV(tokenIn.tok, (isOne ? i1 : i2), tokenIn.value);
979     }
980     int len = 0;
981     boolean isInputSelected = (tokenIn instanceof SV && ((SV) tokenIn).index != Integer.MAX_VALUE);
982     SV tokenOut = newSV(tokenIn.tok, Integer.MAX_VALUE, null);
983 
984     switch (tokenIn.tok) {
985     case bitset:
986       if (tokenIn.value instanceof BondSet) {
987         bs = BondSet.newBS((BS) tokenIn.value,
988             ((BondSet) tokenIn.value).associatedAtoms);
989         len = bs.cardinality();
990       } else {
991         bs = BSUtil.copy((BS) tokenIn.value);
992         len = (isInputSelected ? 1 : bs.cardinality());
993       }
994       break;
995     case barray:
996       len = ((BArray) (((SV) tokenIn).value)).data.length;
997       break;
998     case varray:
999       len = ((SV) tokenIn).getList().size();
1000       break;
1001     case string:
1002       s = (String) tokenIn.value;
1003       len = s.length();
1004       break;
1005     case matrix3f:
1006       len = -3;
1007       break;
1008     case matrix4f:
1009       len = -4;
1010       break;
1011     }
1012 
1013     if (len < 0) {
1014       // matrix mode [1][3] or [13]
1015       len = -len;
1016       if (i1 > 0 && Math.abs(i1) > len) {
1017         int col = i1 % 10;
1018         int row = (i1 - col) / 10;
1019         if (col > 0 && col <= len && row <= len) {
1020           if (tokenIn.tok == matrix3f)
1021             return newV(decimal, Float.valueOf(((M3) tokenIn.value).getElement(
1022                 row - 1, col - 1)));
1023           return newV(decimal,
1024               Float.valueOf(((M4) tokenIn.value).getElement(row - 1, col - 1)));
1025         }
1026         return newV(string, "");
1027       }
1028       if (Math.abs(i1) > len)
1029         return newV(string, "");
1030       float[] data = new float[len];
1031       if (len == 3) {
1032         if (i1 < 0)
1033           ((M3) tokenIn.value).getColumn(-1 - i1, data);
1034         else
1035           ((M3) tokenIn.value).getRow(i1 - 1, data);
1036       } else {
1037         if (i1 < 0)
1038           ((M4) tokenIn.value).getColumn(-1 - i1, data);
1039         else
1040           ((M4) tokenIn.value).getRow(i1 - 1, data);
1041       }
1042       if (isOne)
1043         return getVariableAF(data);
1044       if (i2 < 1 || i2 > len)
1045         return newV(string, "");
1046       return newV(decimal, Float.valueOf(data[i2 - 1]));
1047     }
1048 
1049     // "testing"[0] gives "g"
1050     // "testing"[-1] gives "n"
1051     // "testing"[3][0] gives "sting"
1052     // "testing"[-1][0] gives "ng"
1053     // "testing"[0][-2] gives just "g" as well
1054     // "testing"[-10] gives ""
1055     if (i1 <= 0)
1056       i1 = len + i1;
1057     if (!isOne) {
1058       if (i1 < 1)
1059         i1 = 1;
1060       if (i2 == 0)
1061         i2 = len;
1062       else if (i2 < 0)
1063         i2 = len + i2;
1064       if (i2 < i1)
1065         i2 = i1;
1066     }
1067 
1068     switch (tokenIn.tok) {
1069     case bitset:
1070       tokenOut.value = bs;
1071       if (isInputSelected) {
1072         if (i1 > 1)
1073           bs.clearAll();
1074         break;
1075       }
1076       if (isOne) {
1077         // i2 will be Integer.MIN_VALUE at this point
1078         // take care of easy ones the easy way
1079         if (i1 == len) {
1080           // {xxx}[0]
1081           i2 = bs.length() - 1;
1082         } else if (i1 == 1) {
1083           // {xxx}[1]
1084           i2 = bs.nextSetBit(0);
1085         }
1086         if (i2 >= -1) {
1087           bs.clearAll();
1088           if (i2 >= 0)
1089             bs.set(i2);
1090           break;
1091         }
1092         i2 = i1;
1093       }
1094       int n = 0;
1095       for (int j = bs.nextSetBit(0); j >= 0; j = bs.nextSetBit(j + 1))
1096         if (++n < i1 || n > i2)
1097           bs.clear(j);
1098       break;
1099     case string:
1100       tokenOut.value = (--i1 < 0 || i1 >= len ? "" : isOne ? s.substring(i1,
1101           i1 + 1) : s.substring(i1, Math.min(i2, len)));
1102       break;
1103     case varray:
1104       if (--i1 < 0 || i1 >= len)
1105         return newV(string, "");
1106       if (isOne)
1107         return ((SV) tokenIn).getList().get(i1);
1108       Lst<SV> o2 = new Lst<SV>();
1109       Lst<SV> o1 = ((SV) tokenIn).getList();
1110 
1111       int nn = Math.min(i2, len) - i1;
1112       for (int i = 0; i < nn; i++)
1113         o2.addLast(newT(o1.get(i + i1)));
1114       tokenOut.value = o2;
1115       break;
1116     case barray:
1117       if (--i1 < 0 || i1 >= len)
1118         return newV(string, "");
1119       byte[] data = ((BArray) (((SV) tokenIn).value)).data;
1120       if (isOne)
1121         return newI(data[i1]);
1122       byte[] b = new byte[Math.min(i2, len) - i1];
1123       for (int i = b.length; --i >= 0;)
1124         b[i] = data[i1 + i];
1125       tokenOut.value = new BArray(b);
1126       break;
1127     }
1128     return tokenOut;
1129   }
1130 
setSelectedValue(int pt1, int pt2, SV var)1131   void setSelectedValue(int pt1, int pt2, SV var) {
1132     if (pt1 == Integer.MAX_VALUE)
1133       return;
1134     int len;
1135     switch (tok) {
1136     case matrix3f:
1137     case matrix4f:
1138       len = (tok == matrix3f ? 3 : 4);
1139       if (pt2 != Integer.MAX_VALUE) {
1140         int col = pt2;
1141         int row = pt1;
1142         if (col > 0 && col <= len && row <= len) {
1143           if (tok == matrix3f)
1144             ((M3) value).setElement(row - 1, col - 1, fValue(var));
1145           else
1146             ((M4) value).setElement(row - 1, col - 1, fValue(var));
1147           return;
1148         }
1149       }
1150       if (pt1 != 0 && Math.abs(pt1) <= len
1151           && var.tok == varray) {
1152         Lst<SV> sv = var.getList();
1153         if (sv.size() == len) {
1154           float[] data = new float[len];
1155           for (int i = 0; i < len; i++)
1156             data[i] = fValue(sv.get(i));
1157           if (pt1 > 0) {
1158             if (tok == matrix3f)
1159               ((M3) value).setRowA(pt1 - 1, data);
1160             else
1161               ((M4) value).setRowA(pt1 - 1, data);
1162           } else {
1163             if (tok == matrix3f)
1164               ((M3) value).setColumnA(-1 - pt1, data);
1165             else
1166               ((M4) value).setColumnA(-1 - pt1, data);
1167           }
1168           break;
1169         }
1170       }
1171       break;
1172     case string:
1173       String str = (String) value;
1174       int pt = str.length();
1175       if (pt1 <= 0)
1176         pt1 = pt + pt1;
1177       if (--pt1 < 0)
1178         pt1 = 0;
1179       while (pt1 >= str.length())
1180         str += " ";
1181       if (pt2 == Integer.MAX_VALUE){
1182         pt2 = pt1;
1183       } else {
1184         if (--pt2 < 0)
1185           pt2 = pt + pt2;
1186         while (pt2 >= str.length())
1187           str += " ";
1188       }
1189       if (pt2 >= pt1)
1190         value = str.substring(0, pt1) + sValue(var)
1191           + str.substring(++pt2);
1192       intValue = index = Integer.MAX_VALUE;
1193       break;
1194     case varray:
1195       @SuppressWarnings("unchecked")
1196       Lst<SV> v = (Lst<SV>) value;
1197       len = v.size();
1198       if (pt1 <= 0)
1199         pt1 = len + pt1;
1200       if (--pt1 < 0)
1201         pt1 = 0;
1202       if (len <= pt1)
1203         for (int i = len; i <= pt1; i++)
1204           v.addLast(newV(string, ""));
1205       v.set(pt1, var);
1206       break;
1207     }
1208   }
1209 
escape()1210   public String escape() {
1211     switch (tok) {
1212     case string:
1213       return PT.esc((String) value);
1214     case matrix3f:
1215     case matrix4f:
1216       return PT.toJSON(null, value);
1217     case varray:
1218     case hash:
1219     case context:
1220       SB sb = new SB();
1221       sValueArray(sb, this, "", "", true, false, true, Integer.MAX_VALUE, false);
1222       return sb.toString();
1223     default:
1224       return sValue(this);
1225     }
1226   }
1227 
unescapePointOrBitsetAsVariable(Object o)1228   public static Object unescapePointOrBitsetAsVariable(Object o) {
1229     if (o == null)
1230       return o;
1231     Object v = null;
1232     String s = null;
1233     if (o instanceof SV) {
1234       SV sv = (SV) o;
1235       switch (sv.tok) {
1236       case point3f:
1237       case point4f:
1238       case matrix3f:
1239       case matrix4f:
1240       case bitset:
1241         v = sv.value;
1242         break;
1243       case string:
1244         s = (String) sv.value;
1245         break;
1246       default:
1247         s = sValue(sv);
1248         break;
1249       }
1250     } else if (o instanceof String) {
1251       s = (String) o;
1252     }
1253     if (s != null && s.length() == 0)
1254       return s;
1255     if (v == null)
1256       v = Escape.uABsM(s);
1257     if (v instanceof P3)
1258       return (newV(point3f, v));
1259     if (v instanceof P4)
1260       return newV(point4f, v);
1261     if (v instanceof BS) {
1262       if (s != null && s.indexOf("[{") == 0)
1263         v = BondSet.newBS((BS) v, null);
1264       return newV(bitset, v);
1265     }
1266     if (v instanceof M34)
1267       return (newV(v instanceof M3 ? matrix3f : matrix4f, v));
1268     return o;
1269   }
1270 
getBoolean(boolean value)1271   public static SV getBoolean(boolean value) {
1272     return newT(value ? vT : vF);
1273   }
1274 
sprintf(String strFormat, SV var)1275   public static Object sprintf(String strFormat, SV var) {
1276     if (var == null)
1277       return strFormat;
1278     boolean isArray = (var.tok == varray);
1279     int[] vd = (strFormat.indexOf("d") >= 0 || strFormat.indexOf("i") >= 0 ? new int[1]
1280         : null);
1281     float[] vf = (strFormat.indexOf("f") >= 0 ? new float[1] : null);
1282     double[] ve = (strFormat.indexOf("e") >= 0 ? new double[1] : null);
1283     boolean getS = (strFormat.indexOf("s") >= 0);
1284     boolean getP = (strFormat.indexOf("p") >= 0 && (isArray || var.tok == point3f));
1285     boolean getQ = (strFormat.indexOf("q") >= 0 && (isArray || var.tok == point4f));
1286     Object[] of = new Object[] { vd, vf, ve, null, null, null};
1287      if (!isArray)
1288       return sprintf(strFormat, var, of, vd, vf, ve, getS, getP, getQ);
1289     Lst<SV> sv = var.getList();
1290     String[] list2 = new String[sv.size()];
1291     for (int i = 0; i < list2.length; i++)
1292       list2[i] = sprintf(strFormat, sv.get(i), of, vd, vf, ve, getS, getP, getQ);
1293     return list2;
1294   }
1295 
sprintf(String strFormat, SV var, Object[] of, int[] vd, float[] vf, double[] ve, boolean getS, boolean getP, boolean getQ)1296   private static String sprintf(String strFormat, SV var, Object[] of,
1297                                 int[] vd, float[] vf, double[] ve, boolean getS, boolean getP, boolean getQ) {
1298     if (var.tok == hash) {
1299       int pt = strFormat.indexOf("[");
1300       if (pt >= 0) {
1301         int pt1;
1302         var = var.getMap().get(strFormat.substring(pt + 1, pt1 = strFormat.indexOf("]")));
1303         strFormat = strFormat.substring(0, pt) + strFormat.substring(pt1 + 1);
1304       }
1305    }
1306     if (vd != null)
1307       vd[0] = iValue(var);
1308     if (vf != null)
1309       vf[0] = fValue(var);
1310     if (ve != null)
1311       ve[0] = fValue(var);
1312     if (getS)
1313       of[3] = sValue(var);
1314     if (getP)
1315       of[4]= var.value;
1316     if (getQ)
1317       of[5]= var.value;
1318     return PT.sprintf(strFormat, "IFDspq", of );
1319   }
1320 
1321   /**
1322    *
1323    * @param format
1324    * @return 0: JSON, 5: base64, 12: bytearray, 22: array
1325    */
getFormatType(String format)1326   public static int getFormatType(String format) {
1327     return (format.indexOf(";") >= 0 ? -1 :
1328         ";json;base64;bytearray;array;"
1329     //   0    5      12        22
1330         .indexOf(";" + format.toLowerCase() + ";"));
1331   }
1332 
1333   /**
1334    * Accepts arguments from the format() function First argument is a format
1335    * string.
1336    *
1337    * @param args
1338    * @param pt
1339    *        0: to JSON, 5: to base64, 12: to bytearray, 22: to array
1340    * @return formatted string
1341    */
format(SV[] args, int pt)1342   public static Object format(SV[] args, int pt) {
1343     switch (args.length) {
1344     case 0:
1345       return "";
1346     case 1:
1347       return sValue(args[0]);
1348     case 2:
1349       if (pt == Integer.MAX_VALUE)
1350         pt = getFormatType(args[0].asString());
1351       switch (pt) {
1352       case 0:
1353         String name = args[1].myName;
1354         args[1].myName = null;
1355         Object o = args[1].toJSON();
1356         args[1].myName = name;
1357         return o;
1358       case 5:
1359       case 12:
1360       case 22:
1361         byte[] bytes;
1362         switch (args[1].tok) {
1363         case barray:
1364           bytes = AU.arrayCopyByte(((BArray) args[1].value).data, -1);
1365           break;
1366         case varray:
1367           Lst<SV> l = args[1].getList();
1368           if (pt == 22) {
1369             Lst<SV> l1 = new Lst<SV>();
1370             for (int i = l.size(); --i >= 0;)
1371               l1.addLast(l.get(i));
1372             return l1;
1373           }
1374           bytes = new byte[l.size()];
1375           for (int i = bytes.length; --i >= 0;)
1376             bytes[i] = (byte) l.get(i).asInt();
1377           break;
1378         default:
1379           String s = args[1].asString();
1380           if (s.startsWith(";base64,")) {
1381             if (pt == 5)
1382               return s;
1383             bytes = Base64.decodeBase64(s);
1384           } else {
1385             bytes = s.getBytes();
1386           }
1387         }
1388         return (pt == 22 ? getVariable(bytes) : pt == 12 ? new BArray(bytes)
1389             : ";base64," + javajs.util.Base64.getBase64(bytes).toString());
1390       }
1391     }
1392     // use values to replace codes in format string
1393     String[] format = PT.split(PT.rep(sValue(args[0]), "%%", "\1"), "%");
1394     if (format.length == 0)
1395       return "";
1396     SB sb = new SB();
1397     sb.append(format[0]);
1398     for (int i = 1; i < format.length; i++) {
1399       Object ret = sprintf(PT.formatCheck("%" + format[i]),
1400           (args[1].tok == hash ? args[1] : args[1].tok == varray ? args[1]
1401               .getList().get(i - 1) : i < args.length ? args[i] : null));
1402       if (AU.isAS(ret)) {
1403         String[] list = (String[]) ret;
1404         for (int j = 0; j < list.length; j++)
1405           sb.append(list[j]).append("\n");
1406         continue;
1407       }
1408       sb.append((String) ret);
1409     }
1410     return sb.toString();
1411   }
1412 
getBitSet(SV x, boolean allowNull)1413   public static BS getBitSet(SV x, boolean allowNull) {
1414     switch (x.tok) {
1415     case bitset:
1416       // selectItemTok is important here because this may come from setVariable()
1417       // in the case of     a[1].xyz = ptX1
1418       return (BS) (x.index == Integer.MAX_VALUE ? (SV) selectItemTok(x,
1419           Integer.MIN_VALUE) : x).value;
1420     case varray:
1421       return unEscapeBitSetArray(x.getList(), allowNull);
1422     default:
1423       return (allowNull ? null : new BS());
1424     }
1425   }
1426 
1427   /**
1428    * Turn an array of strings in the form of "{n,n,n:n...} or an array of
1429    * integers into a bitset.
1430    *
1431    * @param x
1432    * @param allowNull
1433    * @return bitset (or null if fails and allowNull is false)
1434    */
unEscapeBitSetArray(Lst<SV> x, boolean allowNull)1435   static BS unEscapeBitSetArray(Lst<SV> x, boolean allowNull) {
1436     BS bs = new BS();
1437     for (int i = 0; i < x.size(); i++) {
1438       SV v = x.get(i);
1439       if (v.tok == T.integer && v.intValue >= 0) {
1440         bs.set(v.intValue);
1441       } else if (v.tok == T.varray) {
1442         BS bs2 = unEscapeBitSetArray(v.getList(), true);
1443         if (bs2 == null)
1444           return (allowNull ? null : new BS());
1445         bs.or(bs2);
1446       } else if (!unEscapeBitSet(v, bs)) {
1447         return (allowNull ? null : new BS());
1448       }
1449     }
1450     return bs;
1451   }
1452 
1453 
1454   /**
1455    * For legacy reasons, "x" == "X" but see isLike()
1456    *
1457    * @param x1
1458    * @param x2
1459    * @return x1 == x2
1460    */
areEqual(SV x1, SV x2)1461   public static boolean areEqual(SV x1, SV x2) {
1462     if (x1 == null || x2 == null)
1463       return false;
1464     if (x1.tok == x2.tok) {
1465       switch (x1.tok) {
1466       case integer:
1467         if (x2.tok == integer) {
1468           return x1.intValue == x2.intValue;
1469         }
1470         break;
1471       case string:
1472         return ((String)x1.value).equalsIgnoreCase((String) x2.value);
1473       case bitset:
1474       case barray:
1475       case hash:
1476       case varray:
1477       case context:
1478         return x1.equals(x2);
1479       case point3f:
1480         return (((P3) x1.value).distance((P3) x2.value) < 0.000001);
1481       case point4f:
1482         return (((P4) x1.value).distance4((P4) x2.value) < 0.000001);
1483       case matrix3f:
1484         return ((M3) x1.value).equals(x2.value);
1485       case matrix4f:
1486         return ((M4) x1.value).equals(x2.value);
1487       }
1488     }
1489     return (Math.abs(fValue(x1) - fValue(x2)) < 0.000001);
1490   }
1491 
1492   /**
1493    * a LIKE "x"    a is a string and equals x
1494    * a LIKE "*x"   a is a string and ends with x
1495    * a LIKE "x*"   a is a string and starts with x
1496    * a LIKE "*x*"  a is a string and contains x
1497    *
1498    * @param x1
1499    * @param x2
1500    * @return  x1 LIKE x2
1501    */
isLike(SV x1, SV x2)1502   public static boolean isLike(SV x1, SV x2) {
1503     return (x1 != null && x2 != null
1504         && x1.tok == string && x2.tok == string
1505         && PT.isLike((String)x1.value, (String) x2.value));
1506   }
1507 
1508   protected class Sort implements Comparator<SV> {
1509     private int arrayPt;
1510     private String myKey;
1511 
Sort(int arrayPt, String myKey)1512     protected Sort(int arrayPt, String myKey) {
1513       this.arrayPt = arrayPt;
1514       this.myKey = myKey;
1515     }
1516 
1517     @Override
compare(SV x, SV y)1518     public int compare(SV x, SV y) {
1519       if (x.tok != y.tok) {
1520         if (x.tok == decimal || x.tok == integer || y.tok == decimal
1521             || y.tok == integer) {
1522           float fx = fValue(x);
1523           float fy = fValue(y);
1524           return (fx < fy ? -1 : fx > fy ? 1 : 0);
1525         }
1526         if (x.tok == string || y.tok == string)
1527           return sValue(x).compareTo(sValue(y));
1528       }
1529       switch (x.tok) {
1530       case integer:
1531         return (x.intValue < y.intValue ? -1 : x.intValue > y.intValue ? 1 : 0);
1532       case string:
1533         return sValue(x).compareTo(sValue(y));
1534       case varray:
1535         Lst<SV> sx = x.getList();
1536         Lst<SV> sy = y.getList();
1537         if (sx.size() != sy.size())
1538           return (sx.size() < sy.size() ? -1 : 1);
1539         int iPt = arrayPt;
1540         if (iPt < 0)
1541           iPt += sx.size();
1542         if (iPt < 0 || iPt >= sx.size())
1543           return 0;
1544         return compare(sx.get(iPt), sy.get(iPt));
1545       case hash:
1546         if (myKey != null) {
1547           return compare(x.getMap().get(myKey), y.getMap().get(myKey));
1548         }
1549         //$FALL-THROUGH$
1550       default:
1551         float fx = fValue(x);
1552         float fy = fValue(y);
1553         return (fx < fy ? -1 : fx > fy ? 1 : 0);
1554       }
1555     }
1556   }
1557 
1558   /**
1559    *
1560    * @param arrayPt
1561    *        1-based or Integer.MIN_VALUE to reverse
1562    * @return sorted or reversed array
1563    */
sortOrReverse(int arrayPt)1564   public SV sortOrReverse(int arrayPt) {
1565     Lst<SV> x = getList();
1566     if (x != null && x.size() > 1) {
1567       if (arrayPt == Integer.MIN_VALUE) {
1568         // reverse
1569         int n = x.size();
1570         for (int i = 0; i < n; i++) {
1571           SV v = x.get(i);
1572           x.set(i, x.get(--n));
1573           x.set(n, v);
1574         }
1575       } else {
1576         Collections.sort(getList(), new Sort(--arrayPt, null));
1577       }
1578     }
1579     return this;
1580   }
1581 
1582   /**
1583    *
1584    * Script variables are pushed after cloning, because the name comes with them
1585    * when we do otherwise they are not mutable anyway. We do want to have actual
1586    * references to points, lists, and associative arrays
1587    * @param mapKey
1588    * @param value
1589    *        null to pop
1590    *
1591    * @return array
1592    */
pushPop(SV mapKey, SV value)1593   public SV pushPop(SV mapKey, SV value) {
1594     if (mapKey == null) {
1595       Map<String, SV> m = getMap();
1596       if (m == null) {
1597         Lst<SV> x = getList();
1598         if (value == null || x == null) {
1599           // array.pop()
1600           return (x == null || x.size() == 0 ? newS("") : x.removeItemAt(x
1601               .size() - 1));
1602         }
1603         // array.push(value)
1604         x.addLast(newI(0).setv(value));
1605       } else {
1606         if (value == null) {
1607           // assocArray.pop()
1608           m.clear();   // new Jmol 14.18
1609         } else {
1610           Map<String, SV> m1 = value.getMap();
1611           if (m1 != null)
1612             m.putAll(m1);  // new Jmol 14.18
1613           // assocArray.push(value)
1614         }
1615       }
1616     } else {
1617       Map<String, SV> m = getMap();
1618       if (value == null) {
1619         SV v = null;
1620         if (m == null) {
1621           // array.pop(i)
1622           Lst<SV> lst = getList();
1623           int len = lst.size();
1624           int i = iValue(mapKey) - 1;
1625           if (i < 0)
1626             i += len;
1627           if (i >= 0 && i < len) {
1628             v = lst.removeItemAt(i);
1629           }
1630         } else {
1631           // assocArray.pop(key)
1632           v = m.remove(mapKey.asString());
1633         }
1634         return (v == null ? newS("") : v);
1635       }
1636       if (m != null) {
1637         //assocArray.push(key,value)
1638         m.put(mapKey.asString(), newI(0).setv(value));
1639       }
1640     }
1641     return this;
1642   }
1643 
1644   /**
1645    * Turn the string "({3:5})" into a bitset
1646    * @param x
1647    *
1648    * @param bs
1649    * @return a bitset or a string converted to one
1650    */
unEscapeBitSet(SV x, BS bs)1651   private static boolean unEscapeBitSet(SV x, BS bs) {
1652     switch(x.tok) {
1653     case string:
1654       BS bs1 = BS.unescape((String) x.value);
1655       if (bs1 == null)
1656         return false;
1657       bs.or(bs1);
1658       return true;
1659     case bitset:
1660       bs.or((BS) x.value);
1661       return true;
1662     }
1663     return false;
1664   }
1665 
strListValue(T x)1666   public static String[] strListValue(T x) {
1667     if (x.tok != varray)
1668       return new String[] { sValue(x) };
1669     Lst<SV> sv = ((SV) x).getList();
1670     String[] list = new String[sv.size()];
1671     for (int i = sv.size(); --i >= 0;)
1672       list[i] = sValue(sv.get(i));
1673     return list;
1674   }
1675 
getArrayDepth(T x)1676   public static int getArrayDepth(T x) {
1677     int n = 0;
1678     Lst<SV> sv;
1679     while (x.tok == varray && (sv = ((SV) x).getList()).size() > 0) {
1680       n++;
1681       x = sv.get(0);
1682     }
1683     return n;
1684   }
fflistValue(T x, int nMin)1685   public static float[][] fflistValue(T x, int nMin) {
1686     if (x.tok != varray) {
1687       return new float[][] { new float[] {fValue(x)} };
1688     }
1689     Lst<SV> sv = ((SV) x).getList();
1690     int svlen = sv.size();
1691     float[][] list;
1692     list = AU.newFloat2(svlen);
1693     if (nMin == 0)
1694       nMin = list.length;
1695     for (int i = list.length; --i >= 0;)
1696       list[i] = flistValue(i >= svlen ? null : sv.get(i), 0);
1697     return list;
1698   }
1699 
flistValue(T x, int nMin)1700   public static float[] flistValue(T x, int nMin) {
1701     if (x == null || x.tok != varray)
1702       return new float[] { fValue(x) };
1703     Lst<SV> sv = ((SV) x).getList();
1704     float[] list;
1705     list = new float[Math.max(nMin, sv.size())];
1706     if (nMin == 0)
1707       nMin = list.length;
1708     for (int i = Math.min(sv.size(), nMin); --i >= 0;)
1709       list[i] = fValue(sv.get(i));
1710     return list;
1711   }
1712 
toArray()1713   public SV toArray() {
1714     int dim;
1715     Lst<SV> o2;
1716     M3 m3 = null;
1717     M4 m4 = null;
1718     switch (tok) {
1719     case matrix3f:
1720       m3 = (M3) value;
1721       dim = 3;
1722       break;
1723     case matrix4f:
1724       m4 = (M4) value;
1725       dim = 4;
1726       break;
1727     case varray:
1728       return this;
1729     default:
1730       o2 = new Lst<SV>();
1731       o2.addLast(this);
1732       return newV(varray, o2);
1733     }
1734     o2 = new  Lst<SV>();
1735     for (int i = 0; i < dim; i++) {
1736       float[] a = new float[dim];
1737       if (m3 == null)
1738         m4.getRow(i, a);
1739       else
1740         m3.getRow(i, a);
1741       o2.addLast(getVariableAF(a));
1742     }
1743     return newV(varray, o2);
1744   }
1745 
1746   @SuppressWarnings("unchecked")
mapValue(String key)1747   SV mapValue(String key) {
1748     switch (tok) {
1749     case hash:
1750       return ((Map<String, SV>) value).get(key);
1751     case context:
1752       ScriptContext sc = ((ScriptContext) value);
1753       return (key.equals("_path") ? newS(sc.contextPath) : sc.getVariable(key));
1754     }
1755     return null;
1756   }
1757 
1758   @SuppressWarnings("unchecked")
getList()1759   public Lst<SV> getList() {
1760     return (tok == varray ? (Lst<SV>) value : null);
1761   }
1762 
isScalar(SV x)1763   public static boolean isScalar(SV x) {
1764     switch (x.tok) {
1765     case varray:
1766     case matrix3f:
1767     case matrix4f:
1768       return false;
1769     case string:
1770       return (((String) x.value).indexOf("\n") < 0);
1771     default:
1772       return true;
1773     }
1774   }
1775 
1776   @Override
toJSON()1777   public String toJSON() {
1778     switch (tok) {
1779     case on:
1780     case off:
1781     case integer:
1782     case decimal:
1783       return sValue(this);
1784     case barray:
1785       return PT.byteArrayToJSON(((BArray) value).data);
1786     case context:
1787       return PT.toJSON(null, ((ScriptContext) value).getFullMap());
1788     case varray:
1789     case hash:
1790       if (myName != null) {
1791         myName = null;
1792         return (tok == hash ? "{  }" : "[  ]");
1793       }
1794       myName = "x";
1795       String s = PT.toJSON(null, value);
1796       myName = null;
1797       return s;
1798     default:
1799       return PT.toJSON(null, value);
1800     }
1801   }
1802 
mapGet(String key)1803   public SV mapGet(String key) {
1804     return getMap().get(key);
1805   }
1806 
mapPut(String key, SV v)1807   public void mapPut(String key, SV v) {
1808     getMap().put(key, v);
1809   }
1810 
1811   @SuppressWarnings("unchecked")
getMap()1812   public Map<String, SV> getMap() {
1813     switch (tok) {
1814     case hash:
1815       return (Map<String, SV>) value;
1816     case context:
1817       return ((ScriptContext) value).vars;
1818     }
1819     return null;
1820   }
1821 
getMapKeys(int nLevels, boolean skipEmpty)1822   public String getMapKeys(int nLevels, boolean skipEmpty) {
1823     if (tok != hash)
1824       return "";
1825     SB sb = new SB();
1826     sValueArray(sb, this, "", "", true, false, false, nLevels + 1, skipEmpty);
1827     return sb.toString();
1828   }
1829 
1830   @Override
toString()1831   public String toString() {
1832     return toString2() + "[" + myName + " index =" + index + " intValue=" + intValue + "]";
1833   }
1834 
getKeys(boolean isAll)1835   public String[] getKeys(boolean isAll) {
1836     switch (tok) {
1837     case hash:
1838     case context:
1839     case varray:
1840       break;
1841     default:
1842       return null;
1843     }
1844     Lst<String> keys = new Lst<String>();
1845     getKeyList(isAll, keys, "");
1846     String[] skeys = keys.toArray(new String[keys.size()]);
1847     Arrays.sort(skeys);
1848     return skeys;
1849   }
1850 
getKeyList(boolean isAll, Lst<String> keys, String prefix)1851   private void getKeyList(boolean isAll, Lst<String> keys, String prefix) {
1852     Map<String, SV> map = getMap();
1853     if (map == null) {
1854       if (isAll) {
1855         Lst<SV> lst;
1856         int n;
1857         if ((lst = getList()) != null && (n = lst.size()) > 0)
1858           lst.get(n - 1).getKeyList(true, keys, prefix + "." + n + ".");
1859       }
1860       return;
1861     }
1862     for(Entry<String, SV> e: map.entrySet()) {
1863       String k = e.getKey();
1864       if (isAll && (k.length() == 0 || !PT.isLetter(k.charAt(0)))) {
1865         if (prefix.endsWith("."))
1866           prefix = prefix.substring(0, prefix.length() - 1);
1867         k = "[" + PT.esc(k) + "]";
1868       }
1869       keys.addLast(prefix + k);
1870       if (isAll)
1871         e.getValue().getKeyList(true, keys, prefix + k + ".");
1872     }
1873   }
1874 
1875   /**
1876    * Copies a hash or array deeply; invoked by Jmol script
1877    *
1878    * x = @a
1879    *
1880    * where a.type == "hash" or a.type == "varray"
1881    *
1882    *
1883    * @param v hash or array
1884    * @param isHash
1885    * @param isDeep TODO
1886    * @return deeply copied variable
1887    */
1888   @SuppressWarnings("unchecked")
deepCopy(Object v, boolean isHash, boolean isDeep)1889   public static Object deepCopy(Object v, boolean isHash, boolean isDeep) {
1890     if (isHash) {
1891       Map<String, SV> vold = (Map<String, SV>) v;
1892       Map<String, SV> vnew = new Hashtable<String, SV>();
1893       for (Entry<String, SV> e : vold.entrySet()) {
1894         SV v1 = e.getValue();
1895         vnew.put(e.getKey(), isDeep ? deepCopySV(v1) : v1);
1896       }
1897       return vnew;
1898     }
1899     Lst<SV> vold2 = (Lst<SV>) v;
1900     Lst<SV> vnew2 = new Lst<SV>();
1901     for (int i = 0, n = vold2.size(); i < n; i++) {
1902       SV vm = vold2.get(i);
1903       vnew2.addLast(isDeep ? deepCopySV(vm) : vm);
1904     }
1905     return vnew2;
1906   }
1907 
deepCopySV(SV vm)1908   private static SV deepCopySV(SV vm) {
1909     switch (vm.tok) {
1910     case hash:
1911     case varray:
1912       if ("\r".equals(vm.myName)) {
1913         vm.myName = null;
1914         vm = SV.newV(vm.tok, (vm.tok == hash ? new Hashtable<String, SV>() : new Lst<SV>()));
1915       } else {
1916         String name0 = vm.myName;
1917         vm.myName = "\r";
1918         SV vm0 = vm;
1919         vm = newV(vm.tok, deepCopy(vm.value, vm.tok == hash, true));
1920         vm0.myName = name0;
1921       }
1922       break;
1923     }
1924     return vm;
1925   }
1926 
sortMapArray(String key)1927   public SV sortMapArray(String key) {
1928     Lst<SV> lst = getList();
1929     if (lst != null) {
1930       Collections.sort(getList(), new Sort(0, key));
1931     }
1932     return this;
1933   }
1934 
1935   /**
1936    * Safely create a JSON key - object pair, allowing for already-named arrays
1937    *
1938    * @param key
1939    * @param property
1940    * @return JSON object
1941    */
safeJSON(String key, Object property)1942   public static Object safeJSON(String key, Object property) {
1943     return "{"
1944         + (property instanceof SV ? PT.esc(key) + " : " + format(new SV[] { null, (SV) property },
1945             0) : PT.toJSON(key, property)) + "}";
1946   }
1947 
1948 }
1949