Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jruby/jruby
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 320437a44c68
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 051cf282dc37
Choose a head ref
  • 6 commits
  • 3 files changed
  • 1 contributor

Commits on Mar 31, 2016

  1. Copy the full SHA
    fcb7229 View commit details
  2. Copy the full SHA
    6b79ef8 View commit details
  3. Copy the full SHA
    f6e3b78 View commit details
  4. Copy the full SHA
    67cf2a1 View commit details
  5. Copy the full SHA
    9f90981 View commit details
  6. Copy the full SHA
    051cf28 View commit details
Showing with 184 additions and 20 deletions.
  1. +4 −4 core/src/main/java/org/jruby/RubyHash.java
  2. +118 −15 core/src/main/java/org/jruby/java/proxies/MapJavaProxy.java
  3. +62 −1 spec/java_integration/types/map_spec.rb
8 changes: 4 additions & 4 deletions core/src/main/java/org/jruby/RubyHash.java
Original file line number Diff line number Diff line change
@@ -1998,9 +1998,9 @@ private IRubyObject any_p_i(ThreadContext context, Block block) {
for (RubyHashEntry entry = head.nextAdded; entry != head; entry = entry.nextAdded) {
IRubyObject newAssoc = RubyArray.newArray(context.runtime, entry.key, entry.value);
if (block.yield(context, newAssoc).isTrue())
return context.getRuntime().getTrue();
return context.runtime.getTrue();
}
return context.getRuntime().getFalse();
return context.runtime.getFalse();
} finally {
iteratorExit();
}
@@ -2011,9 +2011,9 @@ private IRubyObject any_p_i_fast(ThreadContext context, Block block) {
try {
for (RubyHashEntry entry = head.nextAdded; entry != head; entry = entry.nextAdded) {
if (block.yieldArray(context, context.runtime.newArray(entry.key, entry.value), null).isTrue())
return context.getRuntime().getTrue();
return context.runtime.getTrue();
}
return context.getRuntime().getFalse();
return context.runtime.getFalse();
} finally {
iteratorExit();
}
133 changes: 118 additions & 15 deletions core/src/main/java/org/jruby/java/proxies/MapJavaProxy.java
Original file line number Diff line number Diff line change
@@ -34,6 +34,7 @@
import org.jruby.RubyClass;
import org.jruby.RubyFixnum;
import org.jruby.RubyHash;
import org.jruby.RubyProc;
import org.jruby.RubyString;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
@@ -45,15 +46,16 @@
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.invokedynamic.MethodNames;

import java.util.Collection;
import java.util.Map;
import java.util.Set;

/**
* A proxy for wrapping <code>java.util.Map</code> instances.
*
* @author Yoko Harada
*/

public class MapJavaProxy extends ConcreteJavaProxy {
public final class MapJavaProxy extends ConcreteJavaProxy {

private RubyHashMap wrappedMap;

@@ -251,9 +253,10 @@ public RubyHashEntry internalDeleteEntry(final RubyHashEntry entry) {
@Override
public void visitAll(Visitor visitor) {
final Ruby runtime = getRuntime();
@SuppressWarnings("unchecked")
// NOTE: this is here to make maps act similar to Hash-es which allow modifications while
// iterating (meant from the same thread) ... thus we avoid iterating entrySet() directly
final Map<Object, Object> map = mapDelegate();
final Map.Entry[] entries = map.entrySet().toArray( new Map.Entry[ map.size() ] );
final Map.Entry[] entries = map.entrySet().toArray( new Map.Entry[map.size() ] );
for ( Map.Entry entry : entries ) {
IRubyObject key = JavaUtil.convertJavaToUsableRubyObject(runtime, entry.getKey());
IRubyObject value = JavaUtil.convertJavaToUsableRubyObject(runtime, entry.getValue());
@@ -263,7 +266,7 @@ public void visitAll(Visitor visitor) {

@Override
public RubyBoolean compare(final ThreadContext context, final MethodNames method, IRubyObject other) {
setSize( mapDelegate().size() );
syncSize();
if ( other instanceof RubyHashMap ) {
((RubyHashMap) other).syncSize();
}
@@ -294,6 +297,42 @@ public RubyBoolean getCompareByIdentity_p(ThreadContext context) {
return context.runtime.newBoolean( mapDelegate() instanceof java.util.IdentityHashMap );
}

@Override // re-invent @JRubyMethod(name = "any?")
public IRubyObject any_p(ThreadContext context, Block block) {
if (isEmpty()) return context.runtime.getFalse();

if (!block.isGiven()) return context.runtime.getTrue();

if (block.getSignature().arityValue() > 1) {
return any_p_i_fast(context, block);
}
return any_p_i(context, block);
}

private RubyBoolean any_p_i(ThreadContext context, Block block) {
final Ruby runtime = context.runtime;
for ( Map.Entry entry : entrySet() ) {
final IRubyObject key = JavaUtil.convertJavaToUsableRubyObject(runtime, entry.getKey());
final IRubyObject val = JavaUtil.convertJavaToUsableRubyObject(runtime, entry.getValue());
if ( block.yield(context, RubyArray.newArray(runtime, key, val)).isTrue() ) {
return runtime.getTrue();
}
}
return runtime.getFalse();
}

private RubyBoolean any_p_i_fast(ThreadContext context, Block block) {
final Ruby runtime = context.runtime;
for ( Map.Entry entry : entrySet() ) {
final IRubyObject key = JavaUtil.convertJavaToUsableRubyObject(runtime, entry.getKey());
final IRubyObject val = JavaUtil.convertJavaToUsableRubyObject(runtime, entry.getValue());
if ( block.yieldArray(context, runtime.newArray(key, val), null).isTrue() ) {
return runtime.getTrue();
}
}
return runtime.getFalse();
}

@Override
public RubyHash rb_clear() {
mapDelegate().clear();
@@ -319,6 +358,25 @@ public RubyHash to_hash() {
}
return hash;
}

@Override
public final Set keySet() { return mapDelegate().keySet(); }

@Override
public final Set directKeySet() { return keySet(); }

@Override
public final Collection values() { return mapDelegate().values(); }

@Override
public final Collection directValues() { return values(); }

@Override
public final Set<Map.Entry> entrySet() { return mapDelegate().entrySet(); }

@Override
public final Set directEntrySet() { return entrySet(); }

}

@JRubyMethod(name = "default")
@@ -387,6 +445,11 @@ public RubyArray to_a() {
return getOrCreateRubyHashMap().to_a();
}

@JRubyMethod(name = "to_proc")
public RubyProc to_proc(ThreadContext context) {
return getOrCreateRubyHashMap().to_proc(context);
}

/** rb_hash_to_s
*
*/
@@ -443,6 +506,26 @@ public IRubyObject op_aref(ThreadContext context, IRubyObject key) {
return getOrCreateRubyHashMap().op_aref(context, key);
}

@JRubyMethod(name = "<", required = 1)
public IRubyObject op_lt(ThreadContext context, IRubyObject other) {
return getOrCreateRubyHashMap().op_lt(context, other);
}

@JRubyMethod(name = "<=", required = 1)
public IRubyObject op_le(ThreadContext context, IRubyObject other) {
return getOrCreateRubyHashMap().op_le(context, other);
}

@JRubyMethod(name = ">", required = 1)
public IRubyObject op_gt(ThreadContext context, IRubyObject other) {
return getOrCreateRubyHashMap().op_gt(context, other);
}

@JRubyMethod(name = ">=", required = 1)
public IRubyObject op_ge(ThreadContext context, IRubyObject other) {
return getOrCreateRubyHashMap().op_ge(context, other);
}

/** rb_hash_hash
*
*/
@@ -664,6 +747,11 @@ public RubyArray values_at(ThreadContext context, IRubyObject[] args) {
return getOrCreateRubyHashMap().values_at(context, args);
}

@JRubyMethod(name = "fetch_values", rest = true)
public RubyArray fetch_values(ThreadContext context, IRubyObject[] args, Block block) {
return getOrCreateRubyHashMap().fetch_values(context, args, block);
}

@JRubyMethod(name = "assoc")
public IRubyObject assoc(final ThreadContext context, final IRubyObject obj) {
return getOrCreateRubyHashMap().assoc(context, obj);
@@ -684,6 +772,11 @@ public IRubyObject flatten(ThreadContext context, IRubyObject level) {
return getOrCreateRubyHashMap().flatten(context, level);
}

@JRubyMethod(name = "compare_by_identity")
public IRubyObject getCompareByIdentity(ThreadContext context) {
return this; // has no effect - mostly for compatibility
}

@JRubyMethod(name = "compare_by_identity?")
public IRubyObject getCompareByIdentity_p(ThreadContext context) {
return getOrCreateRubyHashMap().getCompareByIdentity_p(context);
@@ -699,34 +792,44 @@ public IRubyObject rbClone() {
return dupImpl("clone");
}

@JRubyMethod(name = "any?")
public IRubyObject any_p(ThreadContext context, Block block) {
return getOrCreateRubyHashMap().any_p(context, block);
}

@JRubyMethod(name = "dig", required = 1, rest = true)
public IRubyObject dig(ThreadContext context, IRubyObject[] args) {
return getOrCreateRubyHashMap().dig(context, args);
}

@SuppressWarnings("unchecked")
private MapJavaProxy dupImpl(final String method) {
final Map map = getMapObject();
try {
Map newMap = (Map) map.getClass().newInstance();
Map newMap = map.getClass().newInstance();
newMap.putAll(map);
MapJavaProxy proxy = new MapJavaProxy(getRuntime(), metaClass);
proxy.setObject(newMap);
return proxy;
}
catch (InstantiationException ex) {
throw initCause( getRuntime().newNotImplementedError("can't "+ method +" Map of type " + getObject().getClass().getName()), ex);
}
catch (IllegalAccessException ex) {
throw initCause( getRuntime().newNotImplementedError("can't "+ method +" Map of type " + getObject().getClass().getName()), ex);
catch (InstantiationException|IllegalAccessException ex) {
final RaiseException e = getRuntime().newNotImplementedError("can't "+ method +" Map of type " + getObject().getClass().getName());
e.initCause(ex); throw e;
}
}

private static RaiseException initCause(final RaiseException re, final Exception e) {
re.initCause(e); return re;
}

final Map getMapObject() {
return (Map) getObject();
}

@Override
public final RubyHash convertToHash() {
return getOrCreateRubyHashMap();
}

@Deprecated
public IRubyObject op_aset19(ThreadContext context, IRubyObject key, IRubyObject value) {
return getOrCreateRubyHashMap().op_aset19(context, key, value);
}

}
63 changes: 62 additions & 1 deletion spec/java_integration/types/map_spec.rb
Original file line number Diff line number Diff line change
@@ -2,13 +2,74 @@

describe "a java.util.Map instance" do

it "return compared_by_identity for IdentityHashMap" do
it 'return compared_by_identity for IdentityHashMap' do
h = java.util.HashMap.new
expect( h.compare_by_identity? ).to be false
h.compare_by_identity # has no effect
expect( h.compare_by_identity? ).to be false
h = java.util.IdentityHashMap.new
expect( h.compare_by_identity? ).to be true
end

it 'digs like a Hash' do
m3 = java.util.TreeMap.new; m3.put('3'.to_java, obj = java.lang.Object.new)
m1 = java.util.HashMap.new; m1['1'] = { 2 => m3 }
expect( m1.dig(1) ).to be nil
expect( m1.dig('1', 2) ).to be m3
expect( m1.dig('1', 2, '3') ).to be obj
end

it 'compares like a Hash' do
m1 = java.util.HashMap.new; m1['a'] = 1; m1['b'] = 2
m2 = java.util.LinkedHashMap.new; m2['b'] = 2; m2['a'] = 1; m2['c'] = 3
m3 = java.util.Hashtable.new; m3['b'] = 3; m3['a'] = 2
expect( m1 > m2 ).to be false
expect( m1 >= m2 ).to be false
expect( m2 > m1 ).to be true
expect( m1 <= m2 ).to be true
expect( m1 > m1 ).to be false
expect( m3 < m1 ).to be false
expect( m3 > m1 ).to be false
end

it 'compares with a Hash' do
m1 = Hash.new; m1['a'] = 1; m1['b'] = 2
m2 = java.util.LinkedHashMap.new; m2['b'] = 2; m2['a'] = 1; m2['c'] = 3
m3 = java.util.Hashtable.new; m3['b'] = 3; m3['a'] = 2
expect( m1 > m2 ).to be false
expect( m1 >= m2 ).to be false

pending 'TODO need more handling to compare Map-s with Hash-es (as expected)'

expect( m2 > m1 ).to be true
expect( m1 <= m2 ).to be true
expect( m1 > m1 ).to be false
expect( m3 < m1 ).to be false
expect( m3 > m1 ).to be false
expect( m2 >= { 'a' => 1, 'b' => 2 } ).to be true
end

it 'fetch-es values' do
m = java.util.HashMap.new({ '1' => 1, '2' => 2, 3 => '3' })
expect( m.fetch_values(3) ).to eql [ '3' ]
expect( m.fetch_values('2', '1') ).to eql [ 2, 1 ]
expect { m.fetch_values(1) }.to raise_error(KeyError)
end

it 'converts to a proc' do
m = java.util.TreeMap.new({ '1' => 1, '2' => 2 })
expect( m.to_proc.call('3') ).to be nil
expect( m.to_proc.call('1') ).to be 1
end

it 'handles any?' do
h = java.util.Hashtable.new; h[1] = 10; h['2'] = 20
expect( h.any? ).to be true
expect( h.any? { |e, v| v > 10 } ).to be true
expect( h.any? { |e, v| v > 20 } ).to be false
expect( h.any? { |e| e[1] > 10 } ).to be true
end

it "supports Hash-like operations" do
h = java.util.HashMap.new
test_ok(h.kind_of? java.util.Map)