1 package gnu.expr; 2 import java.io.*; 3 import gnu.bytecode.*; 4 import gnu.kawa.functions.IsEqual; 5 import gnu.kawa.util.GeneralHashTable; 6 import gnu.kawa.util.HashUtils; 7 import java.lang.reflect.Array; 8 import java.util.*; 9 import gnu.mapping.*; 10 /* #ifdef use:java.util.regex */ 11 import java.util.regex.*; 12 /* #endif */ 13 14 /** Manages the literals of a Compilation. 15 * Implements ObjectOutput, because we use externalization to determine 16 * how literals get compiled into code that re-creates the literal. */ 17 18 public class LitTable extends GeneralHashTable<Object,Object> 19 implements ObjectOutput 20 { 21 Compilation comp; 22 ClassType mainClass; 23 24 /** A table mapping objects to public static final field literals. 25 * When we a need a literal for a value that is an instance of some 26 * class we automatically search the class for static fields. 27 * We use a {@code Table2D} primarily to make use of weak references, 28 * but we also use the 2nd argument: 29 * {@code staticTable(value, null, defaultValue)} yields a Literal 30 * if there is a public static final field for {@code value}, 31 * and {@code defaultValue} otherwise. 32 * {@code staticTable(class, Boolean.TRUE, null) != null} if and only if 33 * we have scanned {@code class} (a {@code java.lang.Class} object). 34 */ 35 static Table2D staticTable = new Table2D (100); 36 37 int literalsCount; 38 39 /** Remembers literals to initialize (in <clinit>). */ 40 Literal literalsChain; 41 LitTable(Compilation comp)42 public LitTable(Compilation comp) 43 { 44 this.comp = comp; 45 this.mainClass = comp.mainClass; 46 } 47 48 private Object hashKeyCache = null; 49 private int hashCodeCache; 50 51 @Override hash(Object key)52 public int hash(Object key) { 53 if (key == hashKeyCache) 54 return hashCodeCache; 55 int h = comp.immediate ? System.identityHashCode(key) 56 : HashUtils.boundedHash(key); 57 hashKeyCache = key; 58 hashCodeCache = h; 59 return h; 60 } 61 62 @Override matches(Object key1, Object key2)63 protected boolean matches(Object key1, Object key2) { 64 if (comp.immediate) 65 return key1 == key2; 66 return litEquals.apply(key1, key2, null); 67 } 68 emit()69 public void emit() throws IOException 70 { 71 // We use two passes. The first generates the graph of 72 // objects and how they are generated. 73 // The second pass actually emits code. 74 // The reason for using two passes is so we can detect cycles 75 // and sharing using the first pass. This generates better code: 76 // If an object is only used once, and is not a top-level literal, 77 // they we don't need to allocate a Field for it. And if an object 78 // does not cyclically depend on itself, we can allocate *and* 79 // initialize using a single call, which generates better code. 80 81 // Here is the first pass. 82 for (Literal init = literalsChain; init != null; 83 init = init.next) 84 { 85 writeObject(init.value); 86 } 87 88 // Here is the second pass. 89 for (Literal init = literalsChain; init != null; 90 init = init.next) 91 { 92 emit(init, true); 93 } 94 95 // For speedier garbage collection. 96 clear(); 97 literalsCount = 0; 98 } 99 100 Object[] valueStack = new Object[20]; 101 Type[] typeStack = new Type[20]; 102 int stackPointer; 103 push(Object value, Type type)104 void push(Object value, Type type) 105 { 106 if (stackPointer >= valueStack.length) 107 { 108 Object[] newValues = new Object[2 * valueStack.length]; 109 Type[] newTypes = new Type[2 * typeStack.length]; 110 System.arraycopy(valueStack, 0, newValues, 0, stackPointer); 111 System.arraycopy(typeStack, 0, newTypes, 0, stackPointer); 112 valueStack = newValues; 113 typeStack = newTypes; 114 } 115 valueStack[stackPointer] = value; 116 typeStack[stackPointer] = type; 117 stackPointer++; 118 } 119 error(String msg)120 void error(String msg) 121 { 122 throw new Error(msg); 123 } 124 flush()125 public void flush() 126 { 127 } 128 close()129 public void close() 130 { 131 } 132 write(int b)133 public void write(int b) throws IOException 134 { 135 error("cannot handle call to write(int) when externalizing literal"); 136 } 137 writeBytes(String s)138 public void writeBytes(String s) throws IOException 139 { 140 error("cannot handle call to writeBytes(String) when externalizing literal"); 141 } 142 write(byte[] b)143 public void write(byte[] b) throws IOException 144 { 145 error("cannot handle call to write(byte[]) when externalizing literal"); 146 } 147 write(byte[] b, int off, int len)148 public void write(byte[] b, int off, int len) throws IOException 149 { 150 error("cannot handle call to write(byte[],int,int) when externalizing literal"); 151 } 152 writeBoolean(boolean v)153 public void writeBoolean(boolean v) 154 { 155 push(new Boolean(v), Type.booleanType); 156 } 157 writeChar(int v)158 public void writeChar(int v) 159 { 160 push(new Character((char) v), Type.charType); 161 } 162 writeByte(int v)163 public void writeByte(int v) 164 { 165 push(new Byte((byte) v), Type.byteType); 166 } 167 writeShort(int v)168 public void writeShort(int v) 169 { 170 push(new Short((short) v), Type.shortType); 171 } 172 writeInt(int v)173 public void writeInt(int v) 174 { 175 push(new Integer(v), Type.intType); 176 } 177 writeLong(long v)178 public void writeLong(long v) 179 { 180 push(new Long(v), Type.longType); 181 } 182 writeFloat(float v)183 public void writeFloat(float v) 184 { 185 push(new Float(v), Type.floatType); 186 } 187 writeDouble(double v)188 public void writeDouble(double v) 189 { 190 push(new Double(v), Type.doubleType); 191 } 192 writeUTF(String v)193 public void writeUTF(String v) 194 { 195 push(v, Type.string_type); 196 } 197 writeChars(String v)198 public void writeChars(String v) 199 { 200 push(v, Type.string_type); 201 } 202 writeObject(Object obj)203 public void writeObject(Object obj) throws IOException 204 { 205 Literal lit = findLiteral(obj); 206 207 if ((lit.flags & (Literal.WRITTEN|Literal.WRITING)) != 0) 208 { 209 // It is referenced more than once, so we we need a Field 210 // to save the value. 211 if (lit.field == null 212 && obj != null && ! (obj instanceof String)) 213 lit.assign(this); 214 if ((lit.flags & Literal.WRITTEN) == 0) 215 lit.flags |= Literal.CYCLIC; 216 } 217 else 218 { 219 lit.flags |= Literal.WRITING; 220 int oldStack = stackPointer; 221 if (obj instanceof gnu.lists.FString 222 && ((gnu.lists.FString) obj).size() < 65535) 223 { // Optimization. 224 push(obj.toString(), Type.string_type); 225 } 226 else if (obj instanceof Externalizable) 227 { 228 ((Externalizable) obj).writeExternal(this); 229 } 230 else if (obj instanceof Object[]) 231 { 232 Object[] arr = (Object[]) obj; 233 for (int i = 0; i < arr.length; i++) 234 { 235 writeObject(arr[i]); 236 } 237 } 238 else if (obj == null 239 || obj instanceof String || lit.type instanceof ArrayType) 240 { 241 // nothing to do 242 } 243 else if (obj instanceof java.math.BigInteger) 244 { 245 writeChars(obj.toString()); 246 } 247 else if (obj instanceof java.math.BigDecimal) 248 { 249 java.math.BigDecimal dec = (java.math.BigDecimal) obj; 250 /* #ifdef JAVA2 */ 251 writeObject(dec.unscaledValue()); 252 writeInt(dec.scale()); 253 /* #else */ 254 // writeChars(obj.toString()); 255 /* #endif */ 256 } 257 else if (obj instanceof Integer) 258 push(obj, Type.intType); 259 else if (obj instanceof Short) 260 push(obj, Type.shortType); 261 else if (obj instanceof Byte) 262 push(obj, Type.byteType); 263 else if (obj instanceof Long) 264 push(obj, Type.longType); 265 else if (obj instanceof Double) 266 push(obj, Type.doubleType); 267 else if (obj instanceof Float) 268 push(obj, Type.floatType); 269 else if (obj instanceof Character) 270 push(obj, Type.charType); 271 else if (obj instanceof Class) 272 push(obj, Type.java_lang_Class_type); 273 /* #ifdef use:java.util.regex */ 274 else if (obj instanceof Pattern) 275 { 276 Pattern pat = (Pattern) obj; 277 push(pat.pattern(), Type.string_type); 278 push(Integer.valueOf(pat.flags()), Type.intType); 279 } 280 /* #endif */ 281 else 282 error(obj.getClass().getName()+" does not implement Externalizable"); 283 int nargs = stackPointer - oldStack; 284 if (nargs == 0) 285 { 286 lit.argValues = gnu.mapping.Values.noArgs; 287 lit.argTypes = Type.typeArray0; 288 } 289 else 290 { 291 lit.argValues = new Object[nargs]; 292 lit.argTypes = new Type[nargs]; 293 System.arraycopy(valueStack, oldStack, lit.argValues, 0, nargs); 294 System.arraycopy(typeStack, oldStack, lit.argTypes, 0, nargs); 295 stackPointer = oldStack; 296 } 297 lit.flags |= Literal.WRITTEN; 298 } 299 push(lit, lit.type); 300 } 301 findLiteral(Object value)302 public Literal findLiteral (Object value) 303 { 304 if (value == null) 305 return Literal.nullLiteral; 306 Literal literal = (Literal) get(value); 307 int valueHash = hash(value); // gets cached value 308 if (literal != null) 309 return literal; 310 if (comp.immediate) 311 return new Literal (value, this); 312 Class valueClass = value.getClass(); 313 Type valueType = Type.make(valueClass); 314 315 synchronized (staticTable) 316 { 317 literal = (Literal) staticTable.get(value, null, null); 318 if ((literal == null || literal.value != value) 319 && valueType instanceof ClassType) 320 { 321 if (value instanceof HasOwningField) { 322 Field fld = ((HasOwningField) value).getOwningField(); 323 if (fld != null) { 324 Literal lit = new Literal (value, fld, this); 325 staticTable.put(value, null, lit); 326 return lit; 327 } 328 } 329 // Add all the static final public fields to staticTable. 330 int needed_mod = Access.STATIC | Access.FINAL | Access.PUBLIC; 331 Class fldClass = valueClass; 332 ClassType fldType = (ClassType) valueType; 333 while (staticTable.get(fldClass, Boolean.TRUE, null) == null) 334 { 335 // This is a convention to note that we've scanned valueType. 336 staticTable.put(fldClass, Boolean.TRUE, fldClass); 337 for (Field fld = fldType.getFields(); 338 fld != null; fld = fld.getNext()) 339 { 340 if ((fld.getModifiers() & needed_mod) == needed_mod 341 && ! (fld.getType() instanceof PrimType)) 342 { 343 try 344 { 345 java.lang.reflect.Field rfld = fld.getReflectField(); 346 Object litValue = rfld.get(null); 347 if (litValue == null 348 || ! fldClass.isInstance(litValue)) 349 continue; 350 Literal lit = new Literal (litValue, fld, this); 351 staticTable.put(litValue, null, lit); 352 int litHash = hash(litValue); // gets cached value 353 if (valueHash == litHash 354 && matches(litValue, value)) 355 literal = lit; 356 } 357 catch (Exception ex) 358 { 359 error("caught "+ex+" getting static field "+fld); 360 } 361 } 362 } 363 fldClass = fldClass.getSuperclass(); 364 if (fldClass == null) 365 break; 366 fldType = (ClassType) Type.make(fldClass); 367 } 368 } 369 } 370 371 if (literal == null) 372 literal = new Literal (value, valueType, this); 373 return literal; 374 } 375 getMethod(ClassType type, String name, Literal literal, boolean isStatic)376 Method getMethod (ClassType type, String name, 377 Literal literal, boolean isStatic) 378 { 379 Type[] argTypes = literal.argTypes; 380 Method method = type.getDeclaredMethods(); 381 int argLength = argTypes.length; 382 Method best = null; 383 long bestArrayArgs = 0; 384 boolean ambiguous = false; 385 Type[] bParameters = null; 386 methodLoop: 387 for (; method != null; method = method.getNext()) 388 { 389 if (! name.equals(method.getName())) 390 continue; 391 boolean mstatic = method.getStaticFlag(); 392 if (isStatic != mstatic) 393 continue; 394 // One bit set for each array parameter. 395 long arrayArgs = 0; 396 Type[] mParameters = method.getParameterTypes(); 397 int iarg = 0; int iparam = 0; 398 for (;; iarg++, iparam++) 399 { 400 if (iarg == argLength && iparam == mParameters.length) 401 { 402 if (best == null || (bestArrayArgs != 0 && arrayArgs == 0)) 403 { 404 best = method; 405 bParameters = mParameters; 406 bestArrayArgs = arrayArgs; 407 } 408 else if (arrayArgs == 0) 409 { 410 // Now see which of 'best' and 'method' is more specific. 411 412 // True if we know best cannot be the more specific. 413 boolean not1 = false; 414 // True if we know new method cannot be the more specific. 415 boolean not2 = false; 416 for (int j = argLength; --j >= 0; ) 417 { 418 int c = bParameters[j].compare(mParameters[j]); 419 if (c != 1) 420 { 421 not2 = true; 422 if (not1) 423 break; 424 } 425 if (c != -1) 426 { 427 not1 = true; 428 if (not2) 429 break; 430 } 431 } 432 if (not1) 433 { 434 best = method; 435 bParameters = mParameters; 436 } 437 ambiguous = not1 && not2; 438 } 439 continue methodLoop; // Look for other matches. 440 } 441 if (iarg == argLength || iparam == mParameters.length) 442 continue methodLoop; // fail on this method 443 Type aType = argTypes[iarg]; 444 Type pType = mParameters[iparam]; 445 if (aType.isSubtype(pType)) 446 ; // OK so far 447 else if (pType instanceof ArrayType && iparam < 64 448 && (aType == Type.intType || aType == Type.shortType)) 449 { 450 int count = ((Number) literal.argValues[iarg]).intValue(); 451 if (count < 0 && type.getName().equals("gnu.math.IntNum")) 452 count -= 0x80000000; // special hack for IntNum. 453 Type elementType = ((ArrayType) pType).getComponentType(); 454 if (count < 0 || iarg + count >= argLength) 455 continue methodLoop; // fail on this method 456 else 457 { 458 for (int j = count; --j >= 0; ) 459 { 460 Type t = argTypes[iarg + j + 1]; 461 if (elementType instanceof PrimType 462 ? elementType.getSignature() != t.getSignature() 463 : ! t.isSubtype(elementType)) 464 continue methodLoop; // fail on this method 465 } 466 iarg += count; 467 arrayArgs |= 1 << iparam; 468 } 469 } 470 else 471 { 472 continue methodLoop; // fail on this method 473 } 474 } 475 } 476 if (ambiguous) 477 return null; 478 if (bestArrayArgs != 0) 479 { 480 Object[] args = new Object[bParameters.length]; 481 Type[] types = new Type[bParameters.length]; 482 int iarg = 0; int iparam = 0; 483 for (;; iarg++, iparam++) 484 { 485 if (iarg == argLength) 486 break; 487 Type pType = bParameters[iparam]; 488 if ((bestArrayArgs & (1 << iparam)) == 0) 489 { 490 args[iparam] = literal.argValues[iarg]; 491 types[iparam] = literal.argTypes[iarg]; 492 } 493 else 494 { 495 int count = ((Number) literal.argValues[iarg]).intValue(); 496 boolean isIntNum = type.getName().equals("gnu.math.IntNum"); 497 if (isIntNum) 498 count -= 0x80000000; // special hack for IntNum. 499 Type elementType = ((ArrayType) pType).getComponentType(); 500 types[iparam] = pType; 501 args[iparam] = Array.newInstance(elementType.getReflectClass(), 502 count); 503 Object[] argValues = literal.argValues; 504 if (isIntNum) 505 { 506 // Special kludge for IntNum: words are Externalized 507 // in big-endian (network) order, but the representation 508 // is little-endian. 509 int[] arr = (int[]) args[iparam]; 510 for (int j = count; j > 0; j--) 511 arr[count - j] 512 = ((Integer) argValues[iarg + j]).intValue(); 513 } 514 else 515 { 516 for (int j = count; --j >= 0; ) 517 Array.set(args[iparam], j, argValues[iarg + 1 + j]); 518 } 519 Literal arrayLiteral = new Literal(args[iparam], pType); 520 if (elementType instanceof ObjectType) 521 arrayLiteral.argValues = (Object[]) args[iparam]; 522 args[iparam] = arrayLiteral; 523 iarg += count; 524 } 525 } 526 literal.argValues = args; 527 literal.argTypes = types; 528 } 529 return best; 530 } 531 putArgs(Literal literal, CodeAttr code)532 void putArgs(Literal literal, CodeAttr code) 533 { 534 Type[] argTypes = literal.argTypes; 535 int len = argTypes.length; 536 for (int i = 0; i < len; i++) 537 { 538 Object value = literal.argValues[i]; 539 if (value instanceof Literal) 540 emit((Literal) value, false); 541 else 542 comp.compileConstant(value, new StackTarget(argTypes[i])); 543 } 544 } 545 store(Literal literal, boolean ignore, CodeAttr code)546 private void store (Literal literal, boolean ignore, CodeAttr code) 547 { 548 if (literal.field != null) 549 { 550 if (! ignore) 551 code.emitDup(literal.type); 552 code.emitPutStatic(literal.field); 553 } 554 literal.flags |= Literal.EMITTED; 555 } 556 emit(Literal literal, boolean ignore)557 void emit(Literal literal, boolean ignore) 558 { 559 CodeAttr code = comp.getCode(); 560 if (literal.value == null) 561 { 562 if (! ignore) 563 code.emitPushNull(); 564 } 565 else if (literal.value instanceof String) 566 { 567 if (! ignore) 568 code.emitPushString(literal.value.toString ()); 569 } 570 else if ((literal.flags & Literal.EMITTED) != 0) 571 { 572 if (! ignore) 573 code.emitGetStatic(literal.field); 574 } 575 else if (literal.value instanceof Object[]) 576 { 577 int len = literal.argValues.length; 578 Type elementType = ((ArrayType) literal.type).getComponentType(); 579 code.emitPushInt(len); 580 code.emitNewArray(elementType); 581 int numNonNull = 0; 582 for (int i = 0; i < len; i++) { 583 if (((Literal) literal.argValues[i]).value != null) 584 numNonNull++; 585 } 586 if (numNonNull > 0) 587 code.emitDup(literal.type); 588 store(literal, ignore, code); 589 for (int i = 0; i < len; i++) 590 { 591 Literal el = (Literal) literal.argValues[i]; 592 if (el.value == null) 593 continue; 594 if (--numNonNull > 0) 595 code.emitDup(literal.type); 596 code.emitPushInt(i); 597 emit(el, false); 598 code.emitArrayStore(elementType); 599 } 600 } 601 else if (literal.type instanceof ArrayType) 602 { 603 int length = java.lang.reflect.Array.getLength(literal.value); 604 int nchunks = 0; 605 int count = length; 606 int chunkSize = 6; 607 // "Optimize" big primitive arrays, to reduce code size. 608 // For now only optimize big int[] literals; maybe more later. FIXME 609 // Otherwise srfi14.class becomes bigger than the 64k limit. 610 if (length >= 8 && literal.value instanceof int[]) { 611 nchunks = length / chunkSize; 612 count -= chunkSize * nchunks; 613 } 614 code.emitPushPrimArray(literal.value, length, count, (ArrayType) literal.type); 615 for (int ichunk = 0; ichunk < nchunks; ichunk++) { 616 Method chunkMethod = ClassType.make("gnu.kawa.util.PrimArrayUtils") 617 .getDeclaredMethod("initArray6Int", 2+chunkSize); 618 code.emitPushInt(count); 619 int[] iarr = (int[]) literal.value; 620 for (int j = 0; j < chunkSize; j++) { 621 code.emitPushInt(iarr[count++]); 622 } 623 code.emitInvokeStatic(chunkMethod); 624 } 625 store(literal, ignore, code); 626 } 627 else if (literal.value instanceof Class) 628 { 629 Class clas = (Class) literal.value; 630 if (clas.isPrimitive()) 631 { 632 String cname = clas.getName(); 633 if (cname.equals("int")) 634 cname = "integer"; 635 cname = "java.lang." 636 +Character.toUpperCase(cname.charAt(0)) 637 +cname.substring(1); 638 code.emitGetStatic(ClassType.make(cname).getDeclaredField("TYPE")); 639 } 640 else 641 comp.loadClassRef((ObjectType)Type.make(clas)); 642 store(literal, ignore, code); 643 } 644 else if (literal.value instanceof ClassType 645 && ! ((ClassType) literal.value).isExisting()) 646 { 647 // We need to special case ClassTypes that are (currently) 648 // non-existing, because the corresponding reflective Class 649 // needs to be loaded using the correct ClassLoader. 650 ClassType ct = (ClassType) literal.value; 651 boolean isPair = literal.value instanceof PairClassType; 652 ClassType typeType = isPair ? ClassType.make("gnu.expr.PairClassType") 653 : Compilation.typeType; 654 Type[] atypes = new Type[isPair ? 2 : 1]; 655 for (int i = atypes.length; --i >= 0; ) 656 atypes[i] = Type.javalangClassType; 657 Method meth = typeType.getDeclaredMethod("make", atypes); 658 comp.loadClassRef((ClassType) ct); 659 if (isPair) 660 comp.loadClassRef(((PairClassType) ct).instanceType); 661 code.emitInvokeStatic(meth); 662 code.emitCheckcast(Compilation.typeClassType); 663 store(literal, ignore, code); 664 } 665 else 666 { 667 ClassType type = (ClassType) literal.type; 668 boolean useDefaultInit = (literal.flags & Literal.CYCLIC) != 0; 669 Method method = null; 670 boolean makeStatic = false; 671 if (! useDefaultInit) 672 { 673 // Look for matching "valueOf" or "make" method. 674 // (For backward compatibility for we prefer Symbol's 'make' 675 // method over 'valueOf' - they differ in argument order.) 676 if (! (literal.value instanceof Symbol)) 677 method = getMethod(type, "valueOf", literal, true); 678 else if (literal.value instanceof SimpleSymbol) 679 method = getMethod(Compilation.typeSymbol, "valueOf", literal, true); 680 if (method == null 681 // Values.make has return type Object, so use the constructor. 682 && ! (literal.value instanceof Values)) 683 { 684 String mname = "make"; 685 /* #ifdef use:java.util.regex */ 686 if (literal.value instanceof Pattern) 687 mname = "compile"; 688 /* #endif */ 689 method = getMethod(type, mname, literal, true); 690 } 691 // otherwise look for matching constructor; 692 if (method != null) 693 makeStatic = true; 694 else if (literal.argTypes.length > 0) 695 method = getMethod(type, "<init>", literal, false); 696 697 if (method == null) 698 useDefaultInit = true; 699 } 700 if (useDefaultInit) 701 { 702 method = getMethod(type, "init", literal, false); 703 if (method == null) 704 method = getMethod(type, "set", literal, false); 705 // otherwise error; 706 } 707 if (method == null && literal.argTypes.length > 0) 708 error("no method to construct "+literal.type); 709 if (makeStatic) 710 { 711 putArgs(literal, code); 712 code.emitInvokeStatic(method); 713 } 714 else if (useDefaultInit) 715 { 716 code.emitNew(type); 717 code.emitDup(type); 718 Method init0 = type.getDeclaredMethod("<init>", 0); 719 code.emitInvokeSpecial(init0); 720 } 721 else 722 { 723 code.emitNew(type); 724 code.emitDup(type); 725 putArgs(literal, code); 726 code.emitInvokeSpecial(method); 727 } 728 Method resolveMethod 729 = makeStatic || literal.value instanceof Values ? null 730 : type.getDeclaredMethod("readResolve", 0); 731 if (resolveMethod != null) 732 { 733 code.emitInvokeVirtual(resolveMethod); 734 type.emitCoerceFromObject(code); 735 } 736 store(literal, ignore && ! (useDefaultInit && method != null), code); 737 if (useDefaultInit && method != null) 738 { 739 if (! ignore) 740 code.emitDup(type); 741 putArgs(literal, code); 742 code.emitInvokeVirtual(method); 743 } 744 } 745 } 746 747 /** A modified equality predicate. 748 * Mostly same as IsEqual (Scheme equal? predicate), 749 * but the classes have to match. Also Symbol is handled specially. 750 * This allows combining equivalent literals in the source code. 751 */ 752 static class LitEquals extends IsEqual { LitEquals()753 public LitEquals() { super(null, "(equals-for-literals)"); } 754 apply(Object arg1, Object arg2, Map<Object,ArrayList<Object>> map)755 public boolean apply (Object arg1, Object arg2, 756 Map<Object,ArrayList<Object>> map) { 757 if (arg1 == arg2) 758 return true; 759 if (arg1 == null || arg2 == null 760 // Symbols can be equals even if not == due to namespaces 761 || arg1 instanceof Symbol 762 || arg1.getClass() != arg2.getClass()) 763 return false; 764 765 return super.apply(arg1, arg2, map); 766 } 767 } 768 static final LitEquals litEquals = new LitEquals(); 769 } 770