1 38 package org.jruby; 39 40 import java.io.IOException ; 41 import java.util.AbstractCollection ; 42 import java.util.AbstractSet ; 43 import java.util.ArrayList ; 44 import java.util.Collection ; 45 import java.util.HashMap ; 46 import java.util.Iterator ; 47 import java.util.Map ; 48 import java.util.Set ; 49 import org.jruby.javasupport.JavaUtil; 50 import org.jruby.runtime.Arity; 51 import org.jruby.runtime.Block; 52 import org.jruby.runtime.ClassIndex; 53 import org.jruby.runtime.ThreadContext; 54 import org.jruby.runtime.builtin.IRubyObject; 55 import org.jruby.runtime.callback.Callback; 56 import org.jruby.runtime.marshal.MarshalStream; 57 import org.jruby.runtime.marshal.UnmarshalStream; 58 59 63 public class RubyHash extends RubyObject implements Map { 64 private Map valueMap; 65 private IRubyObject capturedDefaultProc; 67 private static final Callback NIL_DEFAULT_VALUE = new Callback() { 68 public IRubyObject execute(IRubyObject recv, IRubyObject[] args, Block block) { 69 return recv.getRuntime().getNil(); 70 } 71 72 public Arity getArity() { 73 return Arity.optional(); 74 } 75 }; 76 77 private Callback defaultValueCallback; 80 81 private boolean isRehashing = false; 82 83 public RubyHash(Ruby runtime) { 84 this(runtime, runtime.getNil()); 85 } 86 87 public RubyHash(Ruby runtime, IRubyObject defaultValue) { 88 super(runtime, runtime.getClass("Hash")); 89 this.valueMap = new HashMap (); 90 this.capturedDefaultProc = runtime.getNil(); 91 setDefaultValue(defaultValue); 92 } 93 94 public RubyHash(Ruby runtime, Map valueMap, IRubyObject defaultValue) { 95 super(runtime, runtime.getClass("Hash")); 96 this.valueMap = new HashMap (valueMap); 97 this.capturedDefaultProc = runtime.getNil(); 98 setDefaultValue(defaultValue); 99 } 100 101 public int getNativeTypeIndex() { 102 return ClassIndex.HASH; 103 } 104 105 public IRubyObject getDefaultValue(IRubyObject[] args, Block unusedBlock) { 106 if(defaultValueCallback == null || (args.length == 0 && !capturedDefaultProc.isNil())) { 107 return getRuntime().getNil(); 108 } 109 return defaultValueCallback.execute(this, args, Block.NULL_BLOCK); 110 } 111 112 public IRubyObject setDefaultValue(final IRubyObject defaultValue) { 113 capturedDefaultProc = getRuntime().getNil(); 114 if (defaultValue == getRuntime().getNil()) { 115 defaultValueCallback = NIL_DEFAULT_VALUE; 116 } else { 117 defaultValueCallback = new Callback() { 118 public IRubyObject execute(IRubyObject recv, IRubyObject[] args, Block unusedBlock) { 119 return defaultValue; 120 } 121 122 public Arity getArity() { 123 return Arity.optional(); 124 } 125 }; 126 } 127 128 return defaultValue; 129 } 130 131 public void setDefaultProc(final RubyProc newProc) { 132 final IRubyObject self = this; 133 capturedDefaultProc = newProc; 134 defaultValueCallback = new Callback() { 135 public IRubyObject execute(IRubyObject recv, IRubyObject[] args, Block unusedBlock) { 136 IRubyObject[] nargs = args.length == 0 ? new IRubyObject[] { self } : 137 new IRubyObject[] { self, args[0] }; 138 139 return newProc.call(nargs); 140 } 141 142 public Arity getArity() { 143 return Arity.optional(); 144 } 145 }; 146 } 147 148 public IRubyObject default_proc(Block unusedBlock) { 149 return capturedDefaultProc; 150 } 151 152 public Map getValueMap() { 153 return valueMap; 154 } 155 156 public void setValueMap(Map valueMap) { 157 this.valueMap = valueMap; 158 } 159 160 167 private Iterator keyIterator() { 168 return new ArrayList (valueMap.keySet()).iterator(); 169 } 170 171 private Iterator valueIterator() { 172 return new ArrayList (valueMap.values()).iterator(); 173 } 174 175 176 182 private Iterator modifiableEntryIterator() { 183 return valueMap.entrySet().iterator(); 184 } 185 186 193 private Iterator entryIterator() { 194 return new ArrayList (valueMap.entrySet()).iterator(); } 196 197 200 public void modify() { 201 testFrozen("Hash"); 202 if (isTaint() && getRuntime().getSafeLevel() >= 4) { 203 throw getRuntime().newSecurityError("Insecure: can't modify hash"); 204 } 205 } 206 207 private int length() { 208 return valueMap.size(); 209 } 210 211 213 public static RubyHash newHash(Ruby runtime) { 214 return new RubyHash(runtime); 215 } 216 217 public static RubyHash newHash(Ruby runtime, Map valueMap, IRubyObject defaultValue) { 218 assert defaultValue != null; 219 220 return new RubyHash(runtime, valueMap, defaultValue); 221 } 222 223 public IRubyObject initialize(IRubyObject[] args, Block block) { 224 if (block.isGiven()) { 225 setDefaultProc(getRuntime().newProc(false, block)); 226 } else if (args.length > 0) { 227 modify(); 228 229 setDefaultValue(args[0]); 230 } 231 return this; 232 } 233 234 public IRubyObject inspect() { 235 if(!getRuntime().registerInspecting(this)) { 236 return getRuntime().newString("{...}"); 237 } 238 try { 239 final String sep = ", "; 240 final String arrow = "=>"; 241 final StringBuffer sb = new StringBuffer ("{"); 242 boolean firstEntry = true; 243 244 ThreadContext context = getRuntime().getCurrentContext(); 245 246 for (Iterator iter = valueMap.entrySet().iterator(); iter.hasNext(); ) { 247 Map.Entry entry = (Map.Entry ) iter.next(); 248 IRubyObject key = (IRubyObject) entry.getKey(); 249 IRubyObject value = (IRubyObject) entry.getValue(); 250 if (!firstEntry) { 251 sb.append(sep); 252 } 253 254 sb.append(key.callMethod(context, "inspect")).append(arrow); 255 sb.append(value.callMethod(context, "inspect")); 256 firstEntry = false; 257 } 258 sb.append("}"); 259 return getRuntime().newString(sb.toString()); 260 } finally { 261 getRuntime().unregisterInspecting(this); 262 } 263 } 264 265 public RubyFixnum rb_size() { 266 return getRuntime().newFixnum(length()); 267 } 268 269 public RubyBoolean empty_p() { 270 return length() == 0 ? getRuntime().getTrue() : getRuntime().getFalse(); 271 } 272 273 public RubyArray to_a() { 274 Ruby runtime = getRuntime(); 275 RubyArray result = RubyArray.newArray(runtime, length()); 276 277 for(Iterator iter = valueMap.entrySet().iterator(); iter.hasNext();) { 278 Map.Entry entry = (Map.Entry ) iter.next(); 279 result.append(RubyArray.newArray(runtime, (IRubyObject) entry.getKey(), (IRubyObject) entry.getValue())); 280 } 281 return result; 282 } 283 284 public IRubyObject to_s() { 285 if(!getRuntime().registerInspecting(this)) { 286 return getRuntime().newString("{...}"); 287 } 288 try { 289 return to_a().to_s(); 290 } finally { 291 getRuntime().unregisterInspecting(this); 292 } 293 } 294 295 public RubyHash rehash() { 296 modify(); 297 try { 298 isRehashing = true; 299 valueMap = new HashMap (valueMap); 300 } finally { 301 isRehashing = false; 302 } 303 return this; 304 } 305 306 public RubyHash to_hash() { 307 return this; 308 } 309 310 public IRubyObject aset(IRubyObject key, IRubyObject value) { 311 modify(); 312 313 if (!(key instanceof RubyString) || valueMap.get(key) != null) { 314 valueMap.put(key, value); 315 } else { 316 IRubyObject realKey = key.dup(); 317 realKey.setFrozen(true); 318 valueMap.put(realKey, value); 319 } 320 return value; 321 } 322 323 public IRubyObject aref(IRubyObject key) { 324 IRubyObject value = (IRubyObject) valueMap.get(key); 325 326 return value != null ? value : callMethod(getRuntime().getCurrentContext(), "default", new IRubyObject[] {key}); 327 } 328 329 public IRubyObject fetch(IRubyObject[] args, Block block) { 330 if (args.length < 1) { 331 throw getRuntime().newArgumentError(args.length, 1); 332 } 333 IRubyObject key = args[0]; 334 IRubyObject result = (IRubyObject) valueMap.get(key); 335 if (result == null) { 336 if (args.length > 1) return args[1]; 337 338 if (block.isGiven()) return getRuntime().getCurrentContext().yield(key, block); 339 340 throw getRuntime().newIndexError("key not found"); 341 } 342 return result; 343 } 344 345 346 public RubyBoolean has_key(IRubyObject key) { 347 return getRuntime().newBoolean(valueMap.containsKey(key)); 348 } 349 350 public RubyBoolean has_value(IRubyObject value) { 351 return getRuntime().newBoolean(valueMap.containsValue(value)); 352 } 353 354 public RubyHash each(Block block) { 355 return eachInternal(false, block); 356 } 357 358 public RubyHash each_pair(Block block) { 359 return eachInternal(true, block); 360 } 361 362 protected RubyHash eachInternal(boolean aValue, Block block) { 363 ThreadContext context = getRuntime().getCurrentContext(); 364 for (Iterator iter = entryIterator(); iter.hasNext();) { 365 checkRehashing(); 366 Map.Entry entry = (Map.Entry ) iter.next(); 367 block.yield(context, getRuntime().newArray((IRubyObject)entry.getKey(), (IRubyObject)entry.getValue()), null, null, aValue); 368 } 369 return this; 370 } 371 372 373 374 private void checkRehashing() { 375 if (isRehashing) { 376 throw getRuntime().newIndexError("rehash occured during iteration"); 377 } 378 } 379 380 public RubyHash each_value(Block block) { 381 ThreadContext context = getRuntime().getCurrentContext(); 382 for (Iterator iter = valueIterator(); iter.hasNext();) { 383 checkRehashing(); 384 IRubyObject value = (IRubyObject) iter.next(); 385 context.yield(value, block); 386 } 387 return this; 388 } 389 390 public RubyHash each_key(Block block) { 391 ThreadContext context = getRuntime().getCurrentContext(); 392 for (Iterator iter = keyIterator(); iter.hasNext();) { 393 checkRehashing(); 394 IRubyObject key = (IRubyObject) iter.next(); 395 context.yield(key, block); 396 } 397 return this; 398 } 399 400 public RubyArray sort(Block block) { 401 return (RubyArray) to_a().sort_bang(block); 402 } 403 404 public IRubyObject index(IRubyObject value) { 405 for (Iterator iter = valueMap.keySet().iterator(); iter.hasNext(); ) { 406 Object key = iter.next(); 407 if (value.equals(valueMap.get(key))) { 408 return (IRubyObject) key; 409 } 410 } 411 return getRuntime().getNil(); 412 } 413 414 public RubyArray indices(IRubyObject[] indices) { 415 RubyArray values = RubyArray.newArray(getRuntime(), indices.length); 416 417 for (int i = 0; i < indices.length; i++) { 418 values.append(aref(indices[i])); 419 } 420 421 return values; 422 } 423 424 public RubyArray keys() { 425 return RubyArray.newArray(getRuntime(), valueMap.keySet()); 426 } 427 428 public RubyArray rb_values() { 429 return RubyArray.newArray(getRuntime(), valueMap.values()); 430 } 431 432 public IRubyObject equal(IRubyObject other) { 433 if (this == other) { 434 return getRuntime().getTrue(); 435 } else if (!(other instanceof RubyHash)) { 436 return getRuntime().getFalse(); 437 } else if (length() != ((RubyHash)other).length()) { 438 return getRuntime().getFalse(); 439 } 440 441 for (Iterator iter = modifiableEntryIterator(); iter.hasNext();) { 442 checkRehashing(); 443 Map.Entry entry = (Map.Entry ) iter.next(); 444 445 Object value = ((RubyHash)other).valueMap.get(entry.getKey()); 446 if (value == null || !entry.getValue().equals(value)) { 447 return getRuntime().getFalse(); 448 } 449 } 450 return getRuntime().getTrue(); 451 } 452 453 public RubyArray shift() { 454 modify(); 455 Iterator iter = modifiableEntryIterator(); 456 Map.Entry entry = (Map.Entry )iter.next(); 457 iter.remove(); 458 return RubyArray.newArray(getRuntime(), (IRubyObject)entry.getKey(), (IRubyObject)entry.getValue()); 459 } 460 461 public IRubyObject delete(IRubyObject key, Block block) { 462 modify(); 463 IRubyObject result = (IRubyObject) valueMap.remove(key); 464 465 if (result != null) return result; 466 if (block.isGiven()) return getRuntime().getCurrentContext().yield(key, block); 467 468 return getDefaultValue(new IRubyObject[] {key}, null); 469 } 470 471 public RubyHash delete_if(Block block) { 472 reject_bang(block); 473 return this; 474 } 475 476 public RubyHash reject(Block block) { 477 RubyHash result = (RubyHash) dup(); 478 result.reject_bang(block); 479 return result; 480 } 481 482 public IRubyObject reject_bang(Block block) { 483 modify(); 484 boolean isModified = false; 485 ThreadContext context = getRuntime().getCurrentContext(); 486 for (Iterator iter = keyIterator(); iter.hasNext();) { 487 IRubyObject key = (IRubyObject) iter.next(); 488 IRubyObject value = (IRubyObject) valueMap.get(key); 489 IRubyObject shouldDelete = block.yield(context, getRuntime().newArray(key, value), null, null, true); 490 if (shouldDelete.isTrue()) { 491 valueMap.remove(key); 492 isModified = true; 493 } 494 } 495 496 return isModified ? this : getRuntime().getNil(); 497 } 498 499 public RubyHash rb_clear() { 500 modify(); 501 valueMap.clear(); 502 return this; 503 } 504 505 public RubyHash invert() { 506 RubyHash result = newHash(getRuntime()); 507 508 for (Iterator iter = modifiableEntryIterator(); iter.hasNext();) { 509 Map.Entry entry = (Map.Entry ) iter.next(); 510 result.aset((IRubyObject) entry.getValue(), 511 (IRubyObject) entry.getKey()); 512 } 513 return result; 514 } 515 516 public RubyHash update(IRubyObject freshElements, Block block) { 517 modify(); 518 RubyHash freshElementsHash = 519 (RubyHash) freshElements.convertType(RubyHash.class, "Hash", "to_hash"); 520 ThreadContext ctx = getRuntime().getCurrentContext(); 521 if (block.isGiven()) { 522 Map other = freshElementsHash.valueMap; 523 for(Iterator iter = other.keySet().iterator();iter.hasNext();) { 524 IRubyObject key = (IRubyObject)iter.next(); 525 IRubyObject oval = (IRubyObject)valueMap.get(key); 526 if(null == oval) { 527 valueMap.put(key,other.get(key)); 528 } else { 529 valueMap.put(key,ctx.yield(getRuntime().newArrayNoCopy(new IRubyObject[]{key,oval,(IRubyObject)other.get(key)}), block)); 530 } 531 } 532 } else { 533 valueMap.putAll(freshElementsHash.valueMap); 534 } 535 return this; 536 } 537 538 public RubyHash merge(IRubyObject freshElements, Block block) { 539 return ((RubyHash) dup()).update(freshElements, block); 540 } 541 542 public RubyHash replace(IRubyObject replacement) { 543 modify(); 544 RubyHash replacementHash = 545 (RubyHash) replacement.convertType(RubyHash.class, "Hash", "to_hash"); 546 valueMap.clear(); 547 valueMap.putAll(replacementHash.valueMap); 548 defaultValueCallback = replacementHash.defaultValueCallback; 549 return this; 550 } 551 552 public RubyArray values_at(IRubyObject[] argv) { 553 RubyArray result = RubyArray.newArray(getRuntime()); 554 for (int i = 0; i < argv.length; i++) { 555 result.append(aref(argv[i])); 556 } 557 return result; 558 } 559 560 public boolean hasNonProcDefault() { 561 return defaultValueCallback != NIL_DEFAULT_VALUE; 562 } 563 564 public static void marshalTo(RubyHash hash, MarshalStream output) throws IOException { 567 output.writeInt(hash.getValueMap().size()); 568 569 for (Iterator iter = hash.entryIterator(); iter.hasNext();) { 570 Map.Entry entry = (Map.Entry ) iter.next(); 571 572 output.dumpObject((IRubyObject) entry.getKey()); 573 output.dumpObject((IRubyObject) entry.getValue()); 574 } 575 576 if (hash.hasNonProcDefault()) { 578 output.dumpObject(hash.defaultValueCallback.execute(null, NULL_ARRAY, null)); 579 } 580 } 581 582 public static RubyHash unmarshalFrom(UnmarshalStream input, boolean defaultValue) throws IOException { 583 RubyHash result = newHash(input.getRuntime()); 584 input.registerLinkTarget(result); 585 int size = input.unmarshalInt(); 586 for (int i = 0; i < size; i++) { 587 IRubyObject key = input.unmarshalObject(); 588 IRubyObject value = input.unmarshalObject(); 589 result.aset(key, value); 590 } 591 if (defaultValue) { 592 result.setDefaultValue(input.unmarshalObject()); 593 } 594 return result; 595 } 596 597 public Class getJavaClass() { 598 return Map .class; 599 } 600 601 603 public boolean isEmpty() { 604 return valueMap.isEmpty(); 605 } 606 607 public boolean containsKey(Object key) { 608 return keySet().contains(key); 609 } 610 611 public boolean containsValue(Object value) { 612 IRubyObject element = JavaUtil.convertJavaToRuby(getRuntime(), value); 613 614 for (Iterator iter = valueMap.values().iterator(); iter.hasNext(); ) { 615 if (iter.next().equals(element)) { 616 return true; 617 } 618 } 619 return false; 620 } 621 622 public Object get(Object key) { 623 return JavaUtil.convertRubyToJava((IRubyObject) valueMap.get(JavaUtil.convertJavaToRuby(getRuntime(), key))); 624 } 625 626 public Object put(Object key, Object value) { 627 return valueMap.put(JavaUtil.convertJavaToRuby(getRuntime(), key), 628 JavaUtil.convertJavaToRuby(getRuntime(), value)); 629 } 630 631 public Object remove(Object key) { 632 return valueMap.remove(JavaUtil.convertJavaToRuby(getRuntime(), key)); 633 } 634 635 public void putAll(Map map) { 636 for (Iterator iter = map.keySet().iterator(); iter.hasNext();) { 637 Object key = iter.next(); 638 639 put(key, map.get(key)); 640 } 641 } 642 643 644 public Set entrySet() { 645 return new ConversionMapEntrySet(getRuntime(), valueMap.entrySet()); 646 } 647 648 public int size() { 649 return valueMap.size(); 650 } 651 652 public void clear() { 653 valueMap.clear(); 654 } 655 656 public Collection values() { 657 return new AbstractCollection () { 658 public Iterator iterator() { 659 return new IteratorAdapter(entrySet().iterator()) { 660 public Object next() { 661 return ((Map.Entry ) super.next()).getValue(); 662 } 663 }; 664 } 665 666 public int size() { 667 return RubyHash.this.size(); 668 } 669 670 public boolean contains(Object v) { 671 return RubyHash.this.containsValue(v); 672 } 673 }; 674 } 675 676 public Set keySet() { 677 return new AbstractSet () { 678 public Iterator iterator() { 679 return new IteratorAdapter(entrySet().iterator()) { 680 public Object next() { 681 return ((Map.Entry ) super.next()).getKey(); 682 } 683 }; 684 } 685 686 public int size() { 687 return RubyHash.this.size(); 688 } 689 }; 690 } 691 692 696 private static class IteratorAdapter implements Iterator { 697 private Iterator iterator; 698 699 public IteratorAdapter(Iterator iterator) { 700 this.iterator = iterator; 701 } 702 public boolean hasNext() { 703 return iterator.hasNext(); 704 } 705 public Object next() { 706 return iterator.next(); 707 } 708 public void remove() { 709 iterator.remove(); 710 } 711 } 712 713 714 718 private static class ConversionMapEntrySet extends AbstractSet { 719 protected Set mapEntrySet; 720 protected Ruby runtime; 721 722 public ConversionMapEntrySet(Ruby runtime, Set mapEntrySet) { 723 this.mapEntrySet = mapEntrySet; 724 this.runtime = runtime; 725 } 726 public Iterator iterator() { 727 return new ConversionMapEntryIterator(runtime, mapEntrySet.iterator()); 728 } 729 public boolean contains(Object o) { 730 if (!(o instanceof Map.Entry )) { 731 return false; 732 } 733 return mapEntrySet.contains(getRubifiedMapEntry((Map.Entry ) o)); 734 } 735 736 public boolean remove(Object o) { 737 if (!(o instanceof Map.Entry )) { 738 return false; 739 } 740 return mapEntrySet.remove(getRubifiedMapEntry((Map.Entry ) o)); 741 } 742 public int size() { 743 return mapEntrySet.size(); 744 } 745 public void clear() { 746 mapEntrySet.clear(); 747 } 748 private Entry getRubifiedMapEntry(final Map.Entry mapEntry) { 749 return new Map.Entry (){ 750 public Object getKey() { 751 return JavaUtil.convertJavaToRuby(runtime, mapEntry.getKey()); 752 } 753 public Object getValue() { 754 return JavaUtil.convertJavaToRuby(runtime, mapEntry.getValue()); 755 } 756 public Object setValue(Object arg0) { 757 throw new UnsupportedOperationException ("unexpected call in this context"); 759 } 760 }; 761 } 762 } 763 764 768 private static class ConversionMapEntryIterator implements Iterator { 769 private Iterator iterator; 770 private Ruby runtime; 771 772 public ConversionMapEntryIterator(Ruby runtime, Iterator iterator) { 773 this.iterator = iterator; 774 this.runtime = runtime; 775 } 776 777 public boolean hasNext() { 778 return iterator.hasNext(); 779 } 780 781 public Object next() { 782 return new ConversionMapEntry(runtime, ((Map.Entry ) iterator.next())); 783 } 784 785 public void remove() { 786 iterator.remove(); 787 } 788 } 789 790 791 795 private static class ConversionMapEntry implements Map.Entry { 796 private Entry entry; 797 private Ruby runtime; 798 799 public ConversionMapEntry(Ruby runtime, Map.Entry entry) { 800 this.entry = entry; 801 this.runtime = runtime; 802 } 803 804 public Object getKey() { 805 IRubyObject rubyObject = (IRubyObject) entry.getKey(); 806 return JavaUtil.convertRubyToJava(rubyObject, Object .class); 807 } 808 809 public Object getValue() { 810 IRubyObject rubyObject = (IRubyObject) entry.getValue(); 811 return JavaUtil.convertRubyToJava(rubyObject, Object .class); 812 } 813 814 public Object setValue(Object value) { 815 return entry.setValue(JavaUtil.convertJavaToRuby(runtime, value)); 816 } 817 } 818 819 } 820 | Popular Tags |