1 /* $RCSfile$ 2 * $Author$ 3 * $Date$ 4 * $Revision$ 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 Street, Fifth Floor, Boston, MA 23 * 02110-1301, USA. 24 */ 25 26 package org.jmol.util; 27 28 import java.util.Iterator; 29 import java.util.Map; 30 31 import javajs.util.AU; 32 import javajs.util.Lst; 33 import javajs.util.M34; 34 import javajs.util.PT; 35 import javajs.util.Quat; 36 import javajs.util.SB; 37 import javajs.util.A4; 38 import javajs.util.M3; 39 import javajs.util.M4; 40 import javajs.util.P3; 41 import javajs.util.P4; 42 import javajs.util.T3; 43 import javajs.util.V3; 44 45 import org.jmol.api.JmolDataManager; 46 import javajs.util.BS; 47 import org.jmol.script.SV; 48 import org.jmol.script.T; 49 50 51 52 public class Escape { 53 escapeColor(int argb)54 public static String escapeColor(int argb) { 55 return (argb == 0 ? null : "[x" + getHexColorFromRGB(argb) + "]"); 56 } 57 getHexColorFromRGB(int argb)58 public static String getHexColorFromRGB(int argb) { 59 if (argb == 0) 60 return null; 61 String r = "00" + Integer.toHexString((argb >> 16) & 0xFF); 62 r = r.substring(r.length() - 2); 63 String g = "00" + Integer.toHexString((argb >> 8) & 0xFF); 64 g = g.substring(g.length() - 2); 65 String b = "00" + Integer.toHexString(argb & 0xFF); 66 b = b.substring(b.length() - 2); 67 return r + g + b; 68 } 69 70 71 /** 72 * must be its own, because of the possibility of being null 73 * @param xyz 74 * @return {x y z} 75 */ eP(T3 xyz)76 public static String eP(T3 xyz) { 77 if (xyz == null) 78 return "null"; 79 return "{" + xyz.x + " " + xyz.y + " " + xyz.z + "}"; 80 } 81 matrixToScript(Object m)82 public static String matrixToScript(Object m) { 83 return PT.replaceAllCharacters(m.toString(), "\n\r ","").replace('\t',' '); 84 } 85 eP4(P4 x)86 public static String eP4(P4 x) { 87 return "{" + x.x + " " + x.y + " " + x.z + " " + x.w + "}"; 88 } 89 drawQuat(Quat q, String prefix, String id, P3 ptCenter, float scale)90 public static String drawQuat(Quat q, String prefix, String id, P3 ptCenter, 91 float scale) { 92 String strV = " VECTOR " + eP(ptCenter) + " "; 93 if (scale == 0) 94 scale = 1f; 95 return "draw " + prefix + "x" + id + strV 96 + eP(q.getVectorScaled(0, scale)) + " color red\n" 97 + "draw " + prefix + "y" + id + strV 98 + eP(q.getVectorScaled(1, scale)) + " color green\n" 99 + "draw " + prefix + "z" + id + strV 100 + eP(q.getVectorScaled(2, scale)) + " color blue\n"; 101 } 102 103 @SuppressWarnings("unchecked") e(Object x)104 public static String e(Object x) { 105 if (x == null) 106 return "null"; 107 if (PT.isNonStringPrimitive(x)) 108 return x.toString(); 109 if (x instanceof String) 110 return PT.esc((String) x); 111 if (x instanceof Lst<?>) 112 return eV((Lst<SV>) x); 113 if (x instanceof Map) 114 return escapeMap((Map<String, Object>) x); 115 if (x instanceof BS) 116 return eBS((BS) x); 117 if (x instanceof P4) 118 return eP4((P4) x); 119 if (x instanceof T3) 120 return eP((T3) x); 121 if (AU.isAP(x)) 122 return eAP((T3[]) x); 123 if (AU.isAS(x)) 124 return eAS((String[]) x, true); 125 if (x instanceof M34) 126 return PT.rep(PT.rep(x.toString(), "[\n ", "["), "] ]", "]]"); 127 if (AU.isAFF(x)) { 128 // for isosurface functionXY 129 float[][] ff = (float[][])x; 130 SB sb = new SB().append("["); 131 String sep = ""; 132 for (int i = 0; i < ff.length; i++) { 133 sb.append(sep).append(eAF(ff[i])); 134 sep = ","; 135 } 136 sb.append("]"); 137 return sb.toString(); 138 } 139 if (x instanceof A4) { 140 A4 a = (A4) x; 141 return "{" + a.x + " " + a.y + " " + a.z + " " + (float) (a.angle * 180d/Math.PI) + "}"; 142 } 143 if (x instanceof Quat) 144 return ((Quat) x).toString(); 145 String s = PT.nonArrayString(x); 146 return (s == null ? PT.toJSON(null, x) : s); 147 } 148 eV(Lst<SV> list)149 public static String eV(Lst<SV> list) { 150 if (list == null) 151 return PT.esc(""); 152 SB s = new SB(); 153 s.append("["); 154 for (int i = 0; i < list.size(); i++) { 155 if (i > 0) 156 s.append(", "); 157 s.append(escapeNice(list.get(i).asString())); 158 } 159 s.append("]"); 160 return s.toString(); 161 } 162 escapeMap(Map<String, Object> ht)163 public static String escapeMap(Map<String, Object> ht) { 164 SB sb = new SB(); 165 sb.append("{ "); 166 String sep = ""; 167 for (Map.Entry<String, Object> entry : ht.entrySet()) { 168 String key = entry.getKey(); 169 sb.append(sep).append(PT.esc(key)).appendC(':'); 170 Object val = entry.getValue(); 171 if (!(val instanceof SV)) 172 val = SV.getVariable(val); 173 sb.append(((SV)val).escape()); 174 sep = ","; 175 } 176 sb.append(" }"); 177 return sb.toString(); 178 } 179 180 /** 181 * 182 * @param f 183 * @param asArray -- FALSE allows bypassing of escape(Object f); TRUE: unnecssary 184 * @return tabular string 185 */ escapeFloatA(float[] f, boolean asArray)186 public static String escapeFloatA(float[] f, boolean asArray) { 187 if (asArray) 188 return PT.toJSON(null, f); // or just use escape(f) 189 SB sb = new SB(); 190 for (int i = 0; i < f.length; i++) { 191 if (i > 0) 192 sb.appendC('\n'); 193 sb.appendF(f[i]); 194 } 195 return sb.toString(); 196 } 197 escapeFloatAA(float[][] f, boolean addSemi)198 public static String escapeFloatAA(float[][] f, boolean addSemi) { 199 SB sb = new SB(); 200 String eol = (addSemi ? ";\n" : "\n"); 201 for (int i = 0; i < f.length; i++) 202 if (f[i] != null) { 203 if (i > 0) 204 sb.append(eol); 205 for (int j = 0; j < f[i].length; j++) 206 sb.appendF(f[i][j]).appendC('\t'); 207 } 208 return sb.toString(); 209 } 210 escapeFloatAAA(float[][][] f, boolean addSemi)211 public static String escapeFloatAAA(float[][][] f, boolean addSemi) { 212 SB sb = new SB(); 213 String eol = (addSemi ? ";\n" : "\n"); 214 if (f[0] == null || f[0][0] == null) 215 return "0 0 0" + eol; 216 sb.appendI(f.length).append(" ") 217 .appendI(f[0].length).append(" ") 218 .appendI(f[0][0].length); 219 for (int i = 0; i < f.length; i++) 220 if (f[i] != null) { 221 sb.append(eol); 222 for (int j = 0; j < f[i].length; j++) 223 if (f[i][j] != null) { 224 sb.append(eol); 225 for (int k = 0; k < f[i][j].length; k++) 226 sb.appendF(f[i][j][k]).appendC('\t'); 227 } 228 } 229 return sb.toString(); 230 } 231 232 /** 233 * 234 * @param list 235 * list of strings to serialize 236 * @param nicely TODO 237 * @return serialized array 238 */ eAS(String[] list, boolean nicely)239 public static String eAS(String[] list, boolean nicely) { 240 if (list == null) 241 return PT.esc(""); 242 SB s = new SB(); 243 s.append("["); 244 for (int i = 0; i < list.length; i++) { 245 if (i > 0) 246 s.append(", "); 247 s.append(nicely ? escapeNice(list[i]) : PT.esc(list[i])); 248 } 249 s.append("]"); 250 return s.toString(); 251 } 252 eAI(int[] ilist)253 public static String eAI(int[] ilist) { 254 if (ilist == null) 255 return PT.esc(""); 256 SB s = new SB(); 257 s.append("["); 258 for (int i = 0; i < ilist.length; i++) { 259 if (i > 0) 260 s.append(", "); 261 s.appendI(ilist[i]); 262 } 263 return s.append("]").toString(); 264 } 265 eAD(double[] dlist)266 public static String eAD(double[] dlist) { 267 // from isosurface area or volume calc 268 if (dlist == null) 269 return PT.esc(""); 270 SB s = new SB(); 271 s.append("["); 272 for (int i = 0; i < dlist.length; i++) { 273 if (i > 0) 274 s.append(", "); 275 s.appendD(dlist[i]); 276 } 277 return s.append("]").toString(); 278 } 279 eAF(float[] flist)280 public static String eAF(float[] flist) { 281 if (flist == null) 282 return PT.esc(""); 283 SB s = new SB(); 284 s.append("["); 285 for (int i = 0; i < flist.length; i++) { 286 if (i > 0) 287 s.append(", "); 288 s.appendF(flist[i]); 289 } 290 return s.append("]").toString(); 291 } 292 eAP(T3[] plist)293 public static String eAP(T3[] plist) { 294 if (plist == null) 295 return PT.esc(""); 296 SB s = new SB(); 297 s.append("["); 298 for (int i = 0; i < plist.length; i++) { 299 if (i > 0) 300 s.append(", "); 301 s.append(eP(plist[i])); 302 } 303 return s.append("]").toString(); 304 } 305 escapeNice(String s)306 private static String escapeNice(String s) { 307 if (s == null) 308 return "null"; 309 float f = PT.parseFloatStrict(s); 310 return (Float.isNaN(f) ? PT.esc(s) : s); 311 } 312 uABsM(String s)313 public static Object uABsM(String s) { 314 if (s.charAt(0) == '{') 315 return uP(s); 316 if ((isStringArray(s) 317 || s.startsWith("[{") && s.indexOf("[{") == s.lastIndexOf("[{")) 318 && s.indexOf(',') < 0 && s.indexOf('.') < 0 && s.indexOf('-') < 0) 319 return BS.unescape(s); 320 if (s.startsWith("[[")) 321 return unescapeMatrix(s); 322 return s; 323 } 324 isStringArray(String s)325 public static boolean isStringArray(String s) { 326 return s.startsWith("({") && s.lastIndexOf("({") == 0 327 && s.indexOf("})") == s.length() - 2; 328 } uP(String strPoint)329 public static Object uP(String strPoint) { 330 if (strPoint == null || strPoint.length() == 0) 331 return strPoint; 332 String str = strPoint.replace('\n', ' ').trim(); 333 if (str.charAt(0) != '{' || str.charAt(str.length() - 1) != '}') 334 return strPoint; 335 float[] points = new float[5]; 336 int nPoints = 0; 337 str = str.substring(1, str.length() - 1); 338 int[] next = new int[1]; 339 for (; nPoints < 5; nPoints++) { 340 points[nPoints] = PT.parseFloatNext(str, next); 341 if (Float.isNaN(points[nPoints])) { 342 if (next[0] >= str.length() || str.charAt(next[0]) != ',') 343 break; 344 next[0]++; 345 nPoints--; 346 } 347 } 348 if (nPoints == 3) 349 return P3.new3(points[0], points[1], points[2]); 350 if (nPoints == 4) 351 return P4.new4(points[0], points[1], points[2], points[3]); 352 return strPoint; 353 } 354 unescapeMatrix(String strMatrix)355 public static Object unescapeMatrix(String strMatrix) { 356 if (strMatrix == null || strMatrix.length() == 0) 357 return strMatrix; 358 String str = strMatrix.replace('\n', ' ').trim(); 359 if (str.lastIndexOf("[[") != 0 || str.indexOf("]]") != str.length() - 2) 360 return strMatrix; 361 float[] points = new float[16]; 362 str = str.substring(2, str.length() - 2).replace('[',' ').replace(']',' ').replace(',',' '); 363 int[] next = new int[1]; 364 int nPoints = 0; 365 for (; nPoints < 16; nPoints++) { 366 points[nPoints] = PT.parseFloatNext(str, next); 367 if (Float.isNaN(points[nPoints])) { 368 break; 369 } 370 } 371 if (!Float.isNaN(PT.parseFloatNext(str, next))) 372 return strMatrix; // overflow 373 if (nPoints == 9) 374 return M3.newA9(points); 375 if (nPoints == 16) 376 return M4.newA16(points); 377 return strMatrix; 378 } 379 /* 380 public static Object unescapeArray(String strArray) { 381 if (strArray == null || strArray.length() == 0) 382 return strArray; 383 String str = strArray.replace('\n', ' ').replace(',', ' ').trim(); 384 if (str.lastIndexOf("[") != 0 || str.indexOf("]") != str.length() - 1) 385 return strArray; 386 float[] points = Parser.parseFloatArray(str); 387 for (int i = 0; i < points.length; i++) 388 if (Float.isNaN(points[i])) 389 return strArray; 390 return points; 391 } 392 */ eBS(BS bs)393 public static String eBS(BS bs) { 394 return BS.escape(bs, '(', ')'); 395 } 396 eBond(BS bs)397 public static String eBond(BS bs) { 398 return BS.escape(bs, '[', ']'); 399 } 400 401 /** 402 * Used only for getProperty("readable",...) 403 * 404 * @param name 405 * @param info 406 * @return tabular listing, with array types 407 */ toReadable(String name, Object info)408 public static String toReadable(String name, Object info) { 409 SB sb =new SB(); 410 String sep = ""; 411 if (info == null) 412 return "null"; 413 if (PT.isNonStringPrimitive(info)) 414 return packageReadable(name, null, info.toString()); 415 if (info instanceof String) 416 return packageReadable(name, null, PT.esc((String) info)); 417 if (info instanceof SV) 418 return packageReadable(name, null, ((SV) info).escape()); 419 if (AU.isAS(info)) { 420 sb.append("["); 421 int imax = ((String[]) info).length; 422 for (int i = 0; i < imax; i++) { 423 // this fix, from PT.esc to toReadable, is not necessary 424 // for SwingJS 425 sb.append(sep).append(toReadable(null, ((String[]) info)[i])); 426 sep = ","; 427 } 428 sb.append("]"); 429 return packageReadableSb(name, "String[" + imax + "]", sb); 430 } 431 if (AU.isAI(info)) { 432 sb.append("["); 433 int imax = ((int[]) info).length; 434 for (int i = 0; i < imax; i++) { 435 sb.append(sep).appendI(((int[]) info)[i]); 436 sep = ","; 437 } 438 sb.append("]"); 439 return packageReadableSb(name, "int[" + imax + "]", sb); 440 } 441 if (AU.isAF(info)) { 442 sb.append("["); 443 int imax = ((float[]) info).length; 444 for (int i = 0; i < imax; i++) { 445 sb.append(sep).appendF(((float[]) info)[i]); 446 sep = ","; 447 } 448 sb.append("]"); 449 return packageReadableSb(name, "float[" + imax + "]", sb); 450 } 451 if (AU.isAD(info)) { 452 sb.append("["); 453 int imax = ((double[]) info).length; 454 for (int i = 0; i < imax; i++) { 455 sb.append(sep).appendD(((double[]) info)[i]); 456 sep = ","; 457 } 458 sb.append("]"); 459 return packageReadableSb(name, "double[" + imax + "]", sb); 460 } 461 if (AU.isAP(info)) { 462 sb.append("["); 463 int imax = ((T3[]) info).length; 464 for (int i = 0; i < imax; i++) { 465 sb.append(sep).append(eP(((T3[])info)[i])); 466 sep = ","; 467 } 468 sb.append("]"); 469 return packageReadableSb(name, "point3f[" + imax + "]", sb); 470 } 471 if (AU.isASS(info)) { 472 sb.append("["); 473 int imax = ((String[][]) info).length; 474 for (int i = 0; i < imax; i++) { 475 sb.append(sep).append(toReadable(null, ((String[][]) info)[i])); 476 sep = ",\n"; 477 } 478 sb.append("]"); 479 return packageReadableSb(name, "String[" + imax + "][]", sb); 480 } 481 if (AU.isAII(info)) { 482 sb.append("["); 483 int imax = ((int[][]) info).length; 484 for (int i = 0; i < imax; i++) { 485 sb.append(sep).append(toReadable(null, ((int[][]) info)[i])); 486 sep = ","; 487 } 488 sb.append("]"); 489 return packageReadableSb(name, "int[" + imax + "][]", sb); 490 } 491 if (AU.isAFF(info)) { 492 sb.append("[\n"); 493 int imax = ((float[][]) info).length; 494 for (int i = 0; i < imax; i++) { 495 sb.append(sep).append(toReadable(null, ((float[][]) info)[i])); 496 sep = ",\n"; 497 } 498 sb.append("]"); 499 return packageReadableSb(name, "float[][]", sb); 500 } 501 if (AU.isADD(info)) { 502 sb.append("[\n"); 503 int imax = ((double[][]) info).length; 504 for (int i = 0; i < imax; i++) { 505 sb.append(sep).append(toReadable(null, ((double[][]) info)[i])); 506 sep = ",\n"; 507 } 508 sb.append("]"); 509 return packageReadableSb(name, "double[][]", sb); 510 } 511 if (info instanceof Lst<?>) { 512 int imax = ((Lst<?>) info).size(); 513 for (int i = 0; i < imax; i++) { 514 sb.append(toReadable(name + "[" + (i + 1) + "]", ((Lst<?>) info).get(i))); 515 } 516 return packageReadableSb(name, "List[" + imax + "]", sb); 517 } 518 if (info instanceof M34 519 || info instanceof T3 520 || info instanceof P4 521 || info instanceof A4) { 522 sb.append(e(info)); 523 return packageReadableSb(name, null, sb); 524 } 525 if (info instanceof Map<?, ?>) { 526 Iterator<?> e = ((Map<?, ?>) info).keySet().iterator(); 527 while (e.hasNext()) { 528 String key = (String) e.next(); 529 sb.append(toReadable((name == null ? "" : name + ".") + key, 530 ((Map<?, ?>) info).get(key))); 531 } 532 return sb.toString(); 533 } 534 return packageReadable(name, null, PT.toJSON(null, info)); 535 } 536 packageReadableSb(String infoName, String infoType, SB sb)537 private static String packageReadableSb(String infoName, String infoType, 538 SB sb) { 539 return packageReadable(infoName, infoType, sb.toString()); 540 } 541 packageReadable(String infoName, String infoType, String info)542 private static String packageReadable(String infoName, String infoType, 543 String info) { 544 String s = (infoType == null ? "" : infoType + "\t"); 545 if (infoName == null) 546 return s + info; 547 return "\n" + infoName + "\t" + (infoType == null ? "" : "*" + infoType + "\t") + info; 548 } 549 escapeModelFileNumber(int iv)550 public static String escapeModelFileNumber(int iv) { 551 return "" + (iv / 1000000) + "." + (iv % 1000000); 552 } 553 encapsulateData(String name, Object data, int depth)554 public static String encapsulateData(String name, Object data, int depth) { 555 String s; 556 switch (depth) { 557 case JmolDataManager.DATA_TYPE_AF: 558 s = escapeFloatA((float[]) data, false) + ";\n"; 559 break; 560 case JmolDataManager.DATA_TYPE_AFF: 561 s = escapeFloatAA((float[][]) data, true) + ";\n"; 562 break; 563 case JmolDataManager.DATA_TYPE_AFFF: 564 s = escapeFloatAAA((float[][][]) data, true) + ";\n"; 565 break; 566 default: 567 s = data.toString(); 568 break; 569 } 570 return " DATA \"" + name + "\"\n" + s + " END \"" + name + "\";\n"; 571 572 } 573 574 // public static String escapeXml(Object value) { 575 // if (value instanceof String) 576 // return XmlUtil.wrapCdata(value.toString()); 577 // String s = "" + value; 578 // if (s.length() == 0 || s.charAt(0) != '[') 579 // return s; 580 // return XmlUtil.wrapCdata(toReadable(null, value)); 581 // } 582 unescapeUnicode(String s)583 public static String unescapeUnicode(String s) { 584 int ichMax = s.length(); 585 SB sb = SB.newN(ichMax); 586 int ich = 0; 587 while (ich < ichMax) { 588 char ch = s.charAt(ich++); 589 if (ch == '\\' && ich < ichMax) { 590 ch = s.charAt(ich++); 591 switch (ch) { 592 case 'u': 593 if (ich < ichMax) { 594 int unicode = 0; 595 for (int k = 4; --k >= 0 && ich < ichMax;) { 596 char chT = s.charAt(ich); 597 int hexit = getHexitValue(chT); 598 if (hexit < 0) 599 break; 600 unicode <<= 4; 601 unicode += hexit; 602 ++ich; 603 } 604 ch = (char) unicode; 605 } 606 } 607 } 608 sb.appendC(ch); 609 } 610 return sb.toString(); 611 } 612 getHexitValue(char ch)613 public static int getHexitValue(char ch) { 614 if (ch >= 48 && ch <= 57) 615 return ch - 48; 616 else if (ch >= 97 && ch <= 102) 617 return 10 + ch - 97; 618 else if (ch >= 65 && ch <= 70) 619 return 10 + ch - 65; 620 else 621 return -1; 622 } 623 unescapeStringArray(String data)624 public static String[] unescapeStringArray(String data) { 625 // was only used for LOAD "[\"...\",\"....\",...]" (coming from implicit string) 626 // now also used for simulation peaks array from JSpecView, 627 // which double-escapes strings, I guess 628 //TODO -- should recognize '..' as well as "..." ? 629 if (data == null || !data.startsWith("[") || !data.endsWith("]")) 630 return null; 631 Lst<String> v = new Lst<String>(); 632 int[] next = new int[1]; 633 next[0] = 1; 634 while (next[0] < data.length()) { 635 String s = PT.getQuotedStringNext(data, next); 636 if (s == null) 637 return null; 638 v.addLast(PT.rep(s, "\\\"", "\"")); 639 while (next[0] < data.length() && data.charAt(next[0]) != '"') 640 next[0]++; 641 } 642 return v.toArray(new String[v.size()]); 643 } 644 isAV(Object x)645 public static boolean isAV(Object x) { 646 /** 647 * @j2sNative 648 * return Clazz.instanceOf(x[0], org.jmol.script.SV); 649 */ 650 { 651 return x instanceof SV[]; 652 } 653 } 654 655 /** 656 * Jmol-specific post-processing of 657 * the array data returned by Measure.computeHelicalAxis 658 * 659 * @param id 660 * @param tokType 661 * @param a 662 * @param b 663 * @param pts 664 * @return various objects depending upon tokType 665 */ escapeHelical(String id, int tokType, P3 a, P3 b, T3[] pts)666 public static Object escapeHelical(String id, int tokType, P3 a, P3 b, T3[] pts) { 667 // new T3[] { pt_a_prime, n, r, P3.new3(theta, pitch, residuesPerTurn), pt_b_prime }; 668 switch (tokType) { 669 case T.point: 670 return (pts == null ? new P3() : pts[0]); 671 case T.axis: 672 case T.radius: 673 return (pts == null ? new V3() : pts[tokType == T.axis ? 1 : 2]); 674 case T.angle: 675 return Float.valueOf(pts == null ? Float.NaN : pts[3].x); 676 case T.draw: 677 return (pts == null ? "" : "draw ID \"" + id + "\" VECTOR " 678 + Escape.eP(pts[0]) + " " + Escape.eP(pts[1]) + " color " 679 + (pts[3].x < 0 ? "{255.0 200.0 0.0}" : "{255.0 0.0 128.0}")); 680 case T.measure: 681 return (pts == null ? "" : "measure " + Escape.eP(a) + Escape.eP(pts[0]) 682 + Escape.eP(pts[4])) 683 + Escape.eP(b); 684 default: 685 return (pts == null ? new T3[0] : pts); 686 } 687 } 688 689 } 690