rbtree-jruby 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rbenv-version ADDED
@@ -0,0 +1 @@
1
+ jruby-1.7.0
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development, :test do
9
+ gem "shoulda", ">= 0"
10
+ gem "rdoc", "~> 3.12"
11
+ gem "bundler"
12
+ gem "jeweler", "~> 1.8.4"
13
+ gem "rcov", ">= 0"
14
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,38 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activesupport (3.2.6)
5
+ i18n (~> 0.6)
6
+ multi_json (~> 1.0)
7
+ git (1.2.5)
8
+ i18n (0.6.1)
9
+ jeweler (1.8.4)
10
+ bundler (~> 1.0)
11
+ git (>= 1.2.5)
12
+ rake
13
+ rdoc
14
+ json (1.7.5)
15
+ json (1.7.5-java)
16
+ multi_json (1.3.6)
17
+ rake (0.9.2.2)
18
+ rcov (0.9.11)
19
+ rcov (0.9.11-java)
20
+ rdoc (3.12)
21
+ json (~> 1.4)
22
+ shoulda (3.1.1)
23
+ shoulda-context (~> 1.0)
24
+ shoulda-matchers (~> 1.2)
25
+ shoulda-context (1.0.0)
26
+ shoulda-matchers (1.3.0)
27
+ activesupport (>= 3.0.0)
28
+
29
+ PLATFORMS
30
+ java
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ bundler
35
+ jeweler (~> 1.8.4)
36
+ rcov
37
+ rdoc (~> 3.12)
38
+ shoulda
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Isaiah Peng
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,73 @@
1
+ rbtree-jruby
2
+ ============
3
+
4
+ This is a native implementation of RBTree for jruby.
5
+
6
+ From original RBTree c implementation README:
7
+
8
+ RBTree is a sorted associative collection that is implemented with Red-Black Tree. The elements of RBTree are ordered and its interface is the almost same as Hash, so simply you can consider RBTree sorted Hash.
9
+
10
+ Red-Black Tree is a kind of binary tree that automatically balances
11
+ by itself when a node is inserted or deleted. Thus the complexity
12
+ for insert, search and delete is O(log N) in expected and worst
13
+ case. On the other hand the complexity of Hash is O(1). Because
14
+ Hash is unordered the data structure is more effective than
15
+ Red-Black Tree as an associative collection.
16
+
17
+ The elements of RBTree are sorted with natural ordering (by <=>
18
+ method) of its keys or by a comparator(Proc) set by readjust
19
+ method. It means all keys in RBTree should be comparable with each
20
+ other. Or a comparator that takes two arguments of a key should return
21
+ negative, 0, or positive depending on the first argument is less than,
22
+ equal to, or greater than the second one.
23
+
24
+ The interface of RBTree is the almost same as Hash and there are a
25
+ few methods to take advantage of the ordering:
26
+
27
+ * lower_bound, upper_bound, bound
28
+ * first, last
29
+ * shift, pop
30
+ * reverse_each
31
+
32
+ Note: while iterating RBTree (e.g. in a block of each method), it is
33
+ not modifiable, or TypeError is thrown.
34
+
35
+ RBTree supoorts pretty printing using pp.
36
+
37
+ This library contains two classes. One is RBTree and the other is
38
+ MultiRBTree that is a parent class of RBTree. RBTree does not allow
39
+ duplications of keys but MultiRBTree does.
40
+
41
+ ```ruby
42
+ require "rbtree"
43
+
44
+ rbtree = RBTree["c", 10, "a", 20]
45
+ rbtree["b"] = 30
46
+ p rbtree["b"] # => 30
47
+ rbtree.each do |k, v|
48
+ p [k, v]
49
+ end # => ["a", 20] ["b", 30] ["c", 10]
50
+
51
+ mrbtree = MultiRBTree["c", 10, "a", 20, "e", 30, "a", 40]
52
+ p mrbtree.lower_bound("b") # => ["c", 10]
53
+ mrbtree.bound("a", "d") do |k, v|
54
+ p [k, v]
55
+ end # => ["a", 20] ["a", 40] ["c", 10]
56
+ ```
57
+
58
+
59
+ Requirement
60
+ -----------
61
+
62
+ * JRuby
63
+
64
+ Install
65
+ -------
66
+
67
+ $ sudo gem install rbtree-jruby
68
+
69
+ Copyright
70
+ ---------
71
+
72
+ Copyright (c) 2012 Isaiah Peng. See LICENSE.txt for
73
+ further details.
data/Rakefile ADDED
@@ -0,0 +1,90 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+
6
+ include\
7
+ begin
8
+ RbConfig
9
+ rescue NameError
10
+ Config
11
+ end
12
+
13
+ begin
14
+ Bundler.setup(:default, :development)
15
+ rescue Bundler::BundlerError => e
16
+ $stderr.puts e.message
17
+ $stderr.puts "Run `bundle install` to install missing gems"
18
+ exit e.status_code
19
+ end
20
+ require 'rake'
21
+
22
+ require 'jeweler'
23
+ Jeweler::Tasks.new do |gem|
24
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
25
+ gem.name = "rbtree-jruby"
26
+ gem.homepage = "http://github.com/isaiah/rbtree-jruby"
27
+ gem.license = "MIT"
28
+ gem.summary = %Q{RBTree for jruby}
29
+ gem.description = %Q{RBTree is a sorted associative collection that is implemented with Red-Black Tree. The elements of RBTree are ordered and its interface is the almost same as Hash, so simply you can consider RBTree sorted Hash.}
30
+ gem.email = "issaria@gmail.com"
31
+ gem.authors = ["Isaiah Peng"]
32
+ # dependencies defined in Gemfile
33
+ end
34
+ Jeweler::RubygemsDotOrgTasks.new
35
+
36
+ require 'rake/testtask'
37
+ Rake::TestTask.new(:test) do |test|
38
+ test.libs << 'lib' << 'test'
39
+ test.pattern = 'test/**/test_*.rb'
40
+ test.verbose = true
41
+ end
42
+
43
+ require 'rcov/rcovtask'
44
+ Rcov::RcovTask.new do |test|
45
+ test.libs << 'test'
46
+ test.pattern = 'test/**/test_*.rb'
47
+ test.verbose = true
48
+ test.rcov_opts << '--exclude "gems/*"'
49
+ end
50
+
51
+ task :default => :test
52
+
53
+ require 'rdoc/task'
54
+ Rake::RDocTask.new do |rdoc|
55
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
56
+
57
+ rdoc.rdoc_dir = 'rdoc'
58
+ rdoc.title = "rbtree-jruby #{version}"
59
+ rdoc.rdoc_files.include('README*')
60
+ rdoc.rdoc_files.include('lib/**/*.rb')
61
+ end
62
+
63
+ require 'ant'
64
+
65
+ directory "pkg/classes"
66
+
67
+ desc "Clean up build artifacts"
68
+ task :clean do
69
+ rm_rf "pkg/classes"
70
+ rm_rf "lib/rbtree/ext/*.jar"
71
+ end
72
+
73
+ desc "Compile the extension"
74
+ task :compile => "pkg/classes" do |t|
75
+ ant.javac :srcdir => "java", :destdir => t.prerequisites.first,
76
+ :source => "1.5", :target => "1.5", :debug => true,
77
+ :classpath => "${java.class.path}:${sun.boot.class.path}"
78
+ end
79
+
80
+ desc "Build the jar"
81
+ task :jar => [:clean, :compile] do
82
+ ant.jar :basedir => "pkg/classes", :destfile => "lib/rbtree/ext/multi_r_b_tree.jar", :includes => "**/*.class"
83
+ end
84
+
85
+ task :package => :jar
86
+
87
+ desc "Run the specs"
88
+ task :spec => :jar do
89
+ ruby "-S", "spec", "spec"
90
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,6 @@
1
+ package rbtree.ext;
2
+
3
+ public enum Color {
4
+ RED, BLACK;
5
+ }
6
+
@@ -0,0 +1,1183 @@
1
+ package rbtree.ext;
2
+
3
+ import static org.jruby.RubyEnumerator.enumeratorize;
4
+ import static org.jruby.CompatVersion.*;
5
+
6
+ import org.jruby.Ruby;
7
+ import org.jruby.RubyObject;
8
+ import org.jruby.RubyClass;
9
+ import org.jruby.RubyString;
10
+ import org.jruby.RubyFixnum;
11
+ import org.jruby.RubyHash;
12
+ import org.jruby.RubyArray;
13
+ import org.jruby.RubyProc;
14
+ import org.jruby.runtime.Arity;
15
+ import org.jruby.runtime.ThreadContext;
16
+ import org.jruby.runtime.Block;
17
+ import org.jruby.runtime.ObjectMarshal;
18
+ import org.jruby.runtime.ObjectAllocator;
19
+ import org.jruby.runtime.marshal.MarshalStream;
20
+ import org.jruby.runtime.marshal.UnmarshalStream;
21
+ import org.jruby.runtime.builtin.IRubyObject;
22
+ import org.jruby.anno.JRubyClass;
23
+ import org.jruby.anno.JRubyMethod;
24
+ import org.jruby.util.TypeConverter;
25
+ import org.jruby.javasupport.util.RuntimeHelpers;
26
+
27
+ import org.jruby.exceptions.RaiseException;
28
+
29
+ import java.util.concurrent.atomic.AtomicInteger;
30
+
31
+ import java.io.IOException;
32
+ import java.util.ArrayList;
33
+ import java.util.List;
34
+
35
+ @JRubyClass(name = "MultiRBTree")
36
+ public class MultiRBTree extends RubyObject {
37
+ private Node root = NilNode.getInstance();
38
+ private int size = 0;
39
+ private static final int PROCDEFAULT_HASH_F = 1 << 10;
40
+ private static final int DEFAULT_INSPECT_STR_SIZE = 20;
41
+ private IRubyObject ifNone;
42
+ private RubyProc cmpProc;
43
+ private boolean dupes;
44
+
45
+ public static RubyClass createMultiRBTreeClass(Ruby runtime) {
46
+ RubyClass rbtreeClass = runtime.defineClass("MultiRBTree", runtime.getObject(), RBTREE_ALLOCATOR);
47
+ rbtreeClass.setReifiedClass(MultiRBTree.class);
48
+ rbtreeClass.includeModule(runtime.getEnumerable());
49
+ rbtreeClass.setMarshal(RBTREE_MARSHAL);
50
+ rbtreeClass.defineAnnotatedMethods(MultiRBTree.class);
51
+ return rbtreeClass;
52
+ }
53
+
54
+ private static final ObjectAllocator RBTREE_ALLOCATOR = new ObjectAllocator() {
55
+ public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
56
+ return new MultiRBTree(runtime, klazz);
57
+ }
58
+ };
59
+
60
+ public MultiRBTree(final Ruby ruby, RubyClass rubyClass) {
61
+ super(ruby, rubyClass);
62
+ this.dupes = getMetaClass().getRealClass().getName().equals("MultiRBTree");
63
+ this.ifNone = ruby.getNil();
64
+ }
65
+
66
+ @JRubyMethod(name = "initialize", optional = 1)
67
+ public IRubyObject initialize(ThreadContext context, IRubyObject[] args, Block block) {
68
+ if (block.isGiven()) {
69
+ if (args.length > 0) raiseArgumeneError();
70
+ this.ifNone = getRuntime().newProc(Block.Type.PROC, block);
71
+ flags |= PROCDEFAULT_HASH_F;
72
+ } else {
73
+ Arity.checkArgumentCount(getRuntime(), args, 0, 1);
74
+ if (args.length == 1) this.ifNone = args[0];
75
+ }
76
+ return this;
77
+ }
78
+
79
+ private void raiseArgumeneError() {
80
+ throw getRuntime().newArgumentError("wrong number arguments");
81
+ }
82
+
83
+ @JRubyMethod(name = "clear")
84
+ public IRubyObject init() {
85
+ this.root = NilNode.getInstance();
86
+ this.size = 0;
87
+ return this;
88
+ }
89
+
90
+ @JRubyMethod(name = "[]", rest = true, meta = true)
91
+ public static IRubyObject create(final ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
92
+ RubyClass klass = (RubyClass) recv;
93
+ Ruby runtime = context.getRuntime();
94
+ final MultiRBTree rbtree;
95
+ if (args.length == 1) {
96
+ if (klass.getName().equals("RBTree") && args[0].getMetaClass().getRealClass().getName().equals("MultiRBTree"))
97
+ throw runtime.newTypeError("cannot convert MultiRBTree to RBTree");
98
+ if (args[0] instanceof MultiRBTree) {
99
+ rbtree = (MultiRBTree) klass.allocate();
100
+ rbtree.update(context, (MultiRBTree) args[0], Block.NULL_BLOCK);
101
+ return rbtree;
102
+ }
103
+ IRubyObject tmp = TypeConverter.convertToTypeWithCheck(args[0], runtime.getHash(), "to_hash");
104
+ if (!tmp.isNil()) {
105
+ rbtree = (MultiRBTree) klass.allocate();
106
+ RubyHash hash = (RubyHash) tmp;
107
+ hash.visitAll(new RubyHash.Visitor() {
108
+ @Override
109
+ public void visit(IRubyObject key, IRubyObject val) {
110
+ rbtree.internalPut(context, key, val, false);
111
+ }
112
+ });
113
+ return rbtree;
114
+ }
115
+ tmp = TypeConverter.convertToTypeWithCheck(args[0], runtime.getArray(), "to_ary");
116
+ if (!tmp.isNil()) {
117
+ rbtree = (MultiRBTree) klass.allocate();
118
+ RubyArray arr = (RubyArray) tmp;
119
+ for (int i = 0, j = arr.getLength(); i < j; i++) {
120
+ IRubyObject v = TypeConverter.convertToTypeWithCheck(arr.entry(i), runtime.getArray(), "to_ary");
121
+ if (v.isNil()) continue;
122
+ IRubyObject key = runtime.getNil();
123
+ IRubyObject val = runtime.getNil();
124
+ switch(((RubyArray) v).getLength()) {
125
+ case 2:
126
+ val = ((RubyArray) v).entry(1);
127
+ case 1:
128
+ key = ((RubyArray) v).entry(0);
129
+ rbtree.internalPut(context, key, val, false);
130
+ }
131
+ }
132
+ return rbtree;
133
+ }
134
+ }
135
+ if (args.length % 2 != 0) throw runtime.newArgumentError("odd number of arguments");
136
+ rbtree = (MultiRBTree) klass.allocate();
137
+ for (int i = 0; i < args.length; i += 2) {
138
+ rbtree.internalPut(context, args[i], args[i+1], false);
139
+ }
140
+ return rbtree;
141
+ }
142
+
143
+ @JRubyMethod(name = "[]", required = 1)
144
+ public IRubyObject op_aref(ThreadContext context, IRubyObject key) {
145
+ Node node = internalGet(context, (RubyObject) key);
146
+ return node == null ? callMethod(context, "default", key) : node.getValue();
147
+ }
148
+
149
+ @JRubyMethod(name = "[]=", required = 2, compat = RUBY1_8)
150
+ public IRubyObject op_aset(ThreadContext context, IRubyObject key, IRubyObject val) {
151
+ fastASetCheckString(context, key, val);
152
+ return val;
153
+ }
154
+ @JRubyMethod(name = "[]=", required = 2, compat = RUBY1_9)
155
+ public IRubyObject op_aset19(ThreadContext context, IRubyObject key, IRubyObject val) {
156
+ fastASetCheckString19(context, key, val);
157
+ return val;
158
+ }
159
+
160
+ public final void fastASetCheckString(ThreadContext context, IRubyObject key, IRubyObject value) {
161
+ if (key instanceof RubyString) {
162
+ op_asetForString(context, (RubyString) key, value);
163
+ } else {
164
+ internalPut(context, key, value);
165
+ }
166
+ }
167
+
168
+ public final void fastASetCheckString19(ThreadContext context, IRubyObject key, IRubyObject value) {
169
+ if (key.getMetaClass().getRealClass() == context.runtime.getString()) {
170
+ op_asetForString(context, (RubyString) key, value);
171
+ } else {
172
+ internalPut(context, key, value);
173
+ }
174
+ }
175
+
176
+ protected void op_asetForString(ThreadContext context, RubyString key, IRubyObject value) {
177
+ Node node;
178
+ if (!dupes && (node = internalGet(context, (RubyObject) key)) != null) {
179
+ node.setValue(value);
180
+ } else {
181
+ checkIterating();
182
+ if (!key.isFrozen()) {
183
+ key = key.strDup(context.runtime);
184
+ key.setFrozen(true);
185
+ }
186
+ internalPut(context, key, value, false);
187
+ }
188
+ }
189
+
190
+ @JRubyMethod(name = "fetch", required = 1, optional = 1)
191
+ public IRubyObject rbtree_fetch(ThreadContext context, IRubyObject[] args, Block block) {
192
+ if (block.isGiven() && args.length == 2) {
193
+ getRuntime().getWarnings().warn("block supersedes default value argument");
194
+ }
195
+ Node node = internalGet(context, (RubyObject) args[0]);
196
+ if (node != null)
197
+ return node.getValue();
198
+ if (block.isGiven())
199
+ return block.yield(context, args[0]);
200
+ if (args.length == 1)
201
+ throw getRuntime().newIndexError("key not found");
202
+ return args[1];
203
+ }
204
+
205
+ @JRubyMethod(name = "index")
206
+ public IRubyObject rbtree_index(final ThreadContext context, final IRubyObject value) {
207
+ try {
208
+ iteratorVisitAll(new Visitor() {
209
+ public void visit(IRubyObject key, IRubyObject val) {
210
+ if (value.eql(val)) {
211
+ throw new FoundKey(key);
212
+ }
213
+ }
214
+ });
215
+ return null;
216
+ } catch (FoundKey found) {
217
+ return found.key;
218
+ }
219
+ }
220
+
221
+ private void checkCompatible(Ruby runtime, IRubyObject other) {
222
+ if (!(other instanceof MultiRBTree))
223
+ throw runtime.newTypeError(String.format("wrong argument type %s (expected %s)", other.getMetaClass().getRealClass().getName(), "MultiRBTree"));
224
+ if (getMetaClass().getRealClass().getName().equals("RBTree") && other.getMetaClass().getRealClass().getName().equals("MultiRBTree"))
225
+ throw runtime.newTypeError("cannot convert MultiRBTree to RBTree");
226
+ }
227
+
228
+ @JRubyMethod(name = {"update", "merge!"})
229
+ public IRubyObject update(ThreadContext context, IRubyObject other, Block block) {
230
+ Ruby runtime = getRuntime();
231
+ checkCompatible(runtime, other);
232
+ MultiRBTree otherTree = (MultiRBTree) other;
233
+ for (Node node = otherTree.minimum(); !node.isNull(); node = otherTree.successor(node)) {
234
+ if (block.isGiven()) {
235
+ op_aset(context, node.getKey(), block.yieldSpecific(context, node.getKey(), op_aref(context, node.getKey()), node.getValue()));
236
+ } else {
237
+ op_aset(context, node.getKey(), node.getValue());
238
+ }
239
+ }
240
+ return this;
241
+ }
242
+
243
+ @JRubyMethod
244
+ public IRubyObject merge(final ThreadContext context, final IRubyObject other) {
245
+ Ruby runtime = getRuntime();
246
+ // TODO should allow RubyHash
247
+ if (!(other instanceof MultiRBTree)) {
248
+ runtime.newArgumentError(String.format("wrong argument type %s (expected %s)", other.getMetaClass().getRealClass().getName(), "MultiRBTree"));
249
+ }
250
+
251
+ MultiRBTree result = (MultiRBTree) dup();
252
+ MultiRBTree otherTree = (MultiRBTree) other;
253
+ for (Node node = otherTree.minimum(); !node.isNull(); node = otherTree.successor(node)) {
254
+ result.op_aset(context, node.getKey(), node.getValue());
255
+ }
256
+ return result;
257
+ }
258
+
259
+ @JRubyMethod(name = {"has_key?", "key?", "include?", "member?"})
260
+ public IRubyObject has_key_p(final ThreadContext context, IRubyObject key) {
261
+ return internalGet(context, (RubyObject) key) == null ? getRuntime().getFalse() : getRuntime().getTrue();
262
+ }
263
+
264
+ private boolean hasValue(final ThreadContext context, final IRubyObject value) {
265
+ try {
266
+ visitAll(new Visitor() {
267
+ public void visit(IRubyObject key, IRubyObject val) {
268
+ if (equalInternal(context, val, value)) throw FOUND;
269
+ }
270
+ });
271
+ return false;
272
+ } catch (Found found) {
273
+ return true;
274
+ }
275
+ }
276
+
277
+ @JRubyMethod(name = {"has_value?", "value?"})
278
+ public IRubyObject has_value_p(final ThreadContext context, final IRubyObject value) {
279
+ return getRuntime().newBoolean(hasValue(context, value));
280
+ }
281
+
282
+ @JRubyMethod(name = "keys")
283
+ public IRubyObject keys() {
284
+ final RubyArray keys = getRuntime().newArray(size);
285
+ visitAll(new Visitor() {
286
+ public void visit(IRubyObject key, IRubyObject val) {
287
+ keys.append(key);
288
+ }
289
+ });
290
+ return keys;
291
+ }
292
+
293
+ @JRubyMethod(name = "values")
294
+ public IRubyObject values() {
295
+ final RubyArray values = getRuntime().newArray(size);
296
+ visitAll(new Visitor() {
297
+ public void visit(IRubyObject key, IRubyObject val) {
298
+ values.append(val);
299
+ }
300
+ });
301
+ return values;
302
+ }
303
+
304
+ @JRubyMethod(name = "to_hash")
305
+ public IRubyObject to_hash() {
306
+ Ruby runtime = getRuntime();
307
+ if (getMetaClass().getRealClass().getName().equals("MultiRBTree"))
308
+ throw runtime.newTypeError("cannot convert MultiRBTree to Hash");
309
+ final RubyHash hash = new RubyHash(runtime, runtime.getHash());
310
+ hash.default_value_set(ifNone);
311
+ hash.setFlag(flags, true);
312
+ visitAll(new Visitor() {
313
+ public void visit(IRubyObject key, IRubyObject value) {
314
+ hash.fastASet(key, value);
315
+ }
316
+ });
317
+ return hash;
318
+ }
319
+
320
+ @JRubyMethod
321
+ public IRubyObject to_rbtree() {
322
+ return this;
323
+ }
324
+
325
+ @JRubyMethod(name = "readjust", optional = 1)
326
+ public IRubyObject readjust(ThreadContext context, IRubyObject[] args, Block block) {
327
+ RubyProc oldProc = cmpProc;
328
+ RubyProc cmpfunc = null;
329
+ if (block.isGiven()) {
330
+ if (args.length > 0) raiseArgumeneError();
331
+ cmpfunc = getRuntime().newProc(Block.Type.PROC, block);
332
+ } else if (args.length == 1) {
333
+ if (args[0] instanceof RubyProc) {
334
+ cmpfunc = (RubyProc) args[0];
335
+ } else if (args[0].isNil()) {
336
+ cmpfunc = null;
337
+ } else {
338
+ throw getRuntime().newTypeError(String.format("wrong argument type %s (expected %s)", args[0].getMetaClass().getRealClass().getName(), "Proc"));
339
+ }
340
+ }
341
+ MultiRBTree self = (MultiRBTree) this.dup();
342
+ try {
343
+ replaceInternal(context, self, cmpfunc);
344
+ } catch (RaiseException e) {
345
+ replaceInternal(context, self, oldProc);
346
+ throw e;
347
+ }
348
+ return this;
349
+ }
350
+
351
+ @JRubyMethod(name = "default=")
352
+ public IRubyObject setDefaultVal(ThreadContext context, IRubyObject defaultValue) {
353
+ ifNone = defaultValue;
354
+ flags &= ~PROCDEFAULT_HASH_F;
355
+
356
+ return ifNone;
357
+ }
358
+
359
+ @JRubyMethod(name = "default")
360
+ public IRubyObject default_value_get(ThreadContext context) {
361
+ if ((flags & PROCDEFAULT_HASH_F) != 0) {
362
+ return getRuntime().getNil();
363
+ }
364
+ return ifNone;
365
+ }
366
+ @JRubyMethod(name = "default")
367
+ public IRubyObject default_value_get(ThreadContext context, IRubyObject arg) {
368
+ if ((flags & PROCDEFAULT_HASH_F) != 0) {
369
+ return RuntimeHelpers.invoke(context, ifNone, "call", this, arg);
370
+ }
371
+ return ifNone;
372
+ }
373
+
374
+ @JRubyMethod(name = "default_proc")
375
+ public IRubyObject getDefaultProc() {
376
+ return (flags & PROCDEFAULT_HASH_F) != 0 ? ifNone : getRuntime().getNil();
377
+ }
378
+
379
+ @JRubyMethod(name = "cmp_proc")
380
+ public IRubyObject getCmpProc() {
381
+ return this.cmpProc;
382
+ }
383
+
384
+ public MultiRBTree internalPut(ThreadContext context, IRubyObject key, IRubyObject value) {
385
+ return internalPut(context, key, value, true);
386
+ }
387
+
388
+ public MultiRBTree internalPut(ThreadContext context, IRubyObject key, IRubyObject value, boolean checkExisting) {
389
+ if (!dupes && checkExisting) {
390
+ Node node = internalGet(context, (RubyObject) key);
391
+ if (node != null) {
392
+ node.setValue(value);
393
+ return this;
394
+ }
395
+ }
396
+
397
+ Node x = new Node((RubyObject) key, value);
398
+ internalPutHelper(context, x);
399
+ while (x != this.root && x.getParent().getColor() == Color.RED){
400
+ if (x.getParent() == x.getParent().getParent().getLeft()) {
401
+ Node y = x.getParent().getParent().getRight();
402
+ if (!y.isNull() && y.color == Color.RED) {
403
+ x.getParent().setColor(Color.BLACK);
404
+ y.setColor(Color.BLACK);
405
+ x.getParent().getParent().setColor(Color.RED);
406
+ x = x.getParent().getParent();
407
+ } else {
408
+ if (x == x.getParent().getRight()) {
409
+ x = x.getParent();
410
+ leftRotate(x);
411
+ }
412
+ x.getParent().setColor(Color.BLACK);
413
+ x.getParent().getParent().setColor(Color.RED);
414
+ rightRotate(x.getParent().getParent());
415
+ }
416
+ } else {
417
+ Node y = x.getParent().getParent().getLeft();
418
+ if (!y.isNull() && y.isRed()) {
419
+ x.getParent().setBlack();
420
+ y.setBlack();
421
+ x.getParent().getParent().setRed();
422
+ x = x.getParent().getParent();
423
+ } else {
424
+ if (x == x.getParent().getLeft()) {
425
+ x = x.getParent();
426
+ rightRotate(x);
427
+ }
428
+ x.getParent().setBlack();
429
+ x.getParent().getParent().setRed();
430
+ leftRotate(x.getParent().getParent());
431
+ }
432
+ }
433
+ }
434
+ root.setBlack();
435
+ return this;
436
+ }
437
+
438
+ public IRubyObject internalDelete(ThreadContext context, Node z) {
439
+ Node y = (z.getLeft().isNull() || z.getRight().isNull()) ? z : successor(z);
440
+ Node x = y.getLeft().isNull() ? y.getRight() : y.getLeft();
441
+ x.setParent(y.getParent());
442
+ if (y.getParent().isNull()) {
443
+ this.root = x;
444
+ } else {
445
+ if (y == y.getParent().getLeft()) {
446
+ y.getParent().setLeft(x);
447
+ } else {
448
+ y.getParent().setRight(x);
449
+ }
450
+ }
451
+ if (y != z) {
452
+ z.setKey(y.getKey());
453
+ z.setValue(y.getValue());
454
+ }
455
+ if (y.isBlack()) deleteFixup(x);
456
+ this.size -= 1;
457
+
458
+ return newArray(y);
459
+ }
460
+
461
+ private Node minimum() {
462
+ if (this.root.isNull()) {
463
+ return this.root;
464
+ }
465
+ return minimum(this.root);
466
+ }
467
+
468
+ private Node minimum(Node x) {
469
+ while (!x.getLeft().isNull()) {
470
+ x = x.getLeft();
471
+ }
472
+ return x;
473
+ }
474
+
475
+ private Node maximum() {
476
+ if (this.root.isNull()) {
477
+ return this.root;
478
+ }
479
+ return maximum(this.root);
480
+ }
481
+
482
+ private Node maximum(Node x) {
483
+ while (!x.getRight().isNull())
484
+ x = x.getRight();
485
+ return x;
486
+ }
487
+
488
+ // this is wrong, it cannot grant walk all nodes..
489
+ private Node successor(Node x) {
490
+ if (!x.getRight().isNull()) return minimum(x.getRight());
491
+ Node y = x.getParent();
492
+ while (!y.isNull() && x == y.getRight()) {
493
+ x = y;
494
+ y = y.getParent();
495
+ }
496
+ return y;
497
+ }
498
+
499
+ private Node predecessor(Node x) {
500
+ if (!x.getLeft().isNull()) return maximum(x.getLeft());
501
+ Node y = x.getParent();
502
+ while (!y.isNull() && x == y.getLeft()) {
503
+ x = y;
504
+ y = y.getParent();
505
+ }
506
+ return y;
507
+ }
508
+
509
+ @JRubyMethod(name = {"each_pair", "each"})
510
+ public IRubyObject rbtree_each(final ThreadContext context, final Block block) {
511
+ return block.isGiven() ? eachCommon(context, block)
512
+ : enumeratorize(getRuntime(), this, "each");
513
+ }
514
+
515
+ @JRubyMethod
516
+ public IRubyObject each_key(final ThreadContext context, final Block block) {
517
+ return block.isGiven() ? each_keyCommon(context, block) : enumeratorize(context.runtime, this, "each_key");
518
+ }
519
+
520
+ public MultiRBTree each_keyCommon(final ThreadContext context, final Block block) {
521
+ iteratorVisitAll(new Visitor() {
522
+ public void visit(IRubyObject key, IRubyObject val) {
523
+ block.yield(context, key);
524
+ }
525
+ });
526
+ return this;
527
+ }
528
+
529
+ @JRubyMethod
530
+ public IRubyObject each_value(final ThreadContext context, final Block block) {
531
+ return block.isGiven() ? each_valueCommon(context, block) : enumeratorize(context.runtime, this, "each_value");
532
+ }
533
+
534
+ public MultiRBTree each_valueCommon(final ThreadContext context, final Block block) {
535
+ iteratorVisitAll(new Visitor() {
536
+ public void visit(IRubyObject key, IRubyObject val) {
537
+ block.yield(context, val);
538
+ }
539
+ });
540
+ return this;
541
+ }
542
+
543
+ public IRubyObject eachCommon(final ThreadContext context, final Block block) {
544
+ iteratorVisitAll(new Visitor() {
545
+ public void visit(IRubyObject key, IRubyObject value) {
546
+ block.yieldSpecific(context, key, value);
547
+ }
548
+ });
549
+ return this;
550
+ }
551
+
552
+ @JRubyMethod
553
+ public IRubyObject shift(ThreadContext context) {
554
+ return nodeOrDefault(context, minimum(), true);
555
+ }
556
+
557
+ @JRubyMethod
558
+ public IRubyObject pop(ThreadContext context) {
559
+ return nodeOrDefault(context, maximum(), true);
560
+ }
561
+
562
+ @JRubyMethod
563
+ public IRubyObject delete(ThreadContext context, IRubyObject key, Block block) {
564
+ Node node = lower_boundInternal(context, key);
565
+ if (node.isNull()) {
566
+ if (block.isGiven()) {
567
+ return block.yield(context, key);
568
+ }
569
+ return getRuntime().getNil();
570
+ }
571
+ internalDelete(context, node);
572
+ return node.getValue();
573
+ }
574
+
575
+ @JRubyMethod
576
+ public IRubyObject delete_if(final ThreadContext context, final Block block) {
577
+ return block.isGiven() ? delete_ifInternal(context, block) : enumeratorize(context.runtime, this, "delete_if");
578
+ }
579
+
580
+ private IRubyObject delete_ifInternal(final ThreadContext context, final Block block) {
581
+ List<Node> nodeList = new ArrayList<Node>();
582
+ try {
583
+ iteratorEntry();
584
+ for (Node x = minimum(); !x.isNull(); x = successor(x)) {
585
+ if (block.yieldSpecific(context, x.getKey(), x.getValue()).isTrue()) {
586
+ nodeList.add(x);
587
+ }
588
+ }
589
+ // delete backward
590
+ for (int i = nodeList.size() - 1; i >= 0; i--) {
591
+ internalDelete(context, nodeList.get(i));
592
+ }
593
+ } finally {
594
+ iteratorExit();
595
+ }
596
+ return this;
597
+ }
598
+
599
+ @JRubyMethod(name = "reject!")
600
+ public IRubyObject reject_bang(final ThreadContext context, final Block block) {
601
+ return block.isGiven() ? reject_bangInternal(context, block) : enumeratorize(context.runtime, this, "reject!");
602
+ }
603
+
604
+ private IRubyObject reject_bangInternal(ThreadContext context, Block block) {
605
+ int n = size;
606
+ delete_if(context, block);
607
+ if (n == size) return getRuntime().getNil();
608
+ return this;
609
+ }
610
+
611
+ @JRubyMethod
612
+ public IRubyObject reject(final ThreadContext context, final Block block) {
613
+ return block.isGiven() ? rejectInternal(context, block) : enumeratorize(context.runtime, this, "reject");
614
+ }
615
+
616
+ private IRubyObject rejectInternal(ThreadContext context, Block block) {
617
+ return ((MultiRBTree) dup()).reject_bangInternal(context, block);
618
+ }
619
+
620
+ @JRubyMethod
621
+ public IRubyObject select(final ThreadContext context, final Block block) {
622
+ final Ruby runtime = getRuntime();
623
+ if (!block.isGiven()) return enumeratorize(runtime, this, "select");
624
+
625
+ final MultiRBTree rbtree = (MultiRBTree) getMetaClass().getRealClass().allocate();
626
+ iteratorVisitAll(new Visitor() {
627
+ public void visit(IRubyObject key, IRubyObject value) {
628
+ if (block.yieldSpecific(context, key, value).isTrue())
629
+ rbtree.internalPut(context, key, value);
630
+ }
631
+ });
632
+ return rbtree;
633
+ }
634
+
635
+ @JRubyMethod
636
+ public RubyArray to_a() {
637
+ final Ruby runtime = getRuntime();
638
+ final RubyArray result = runtime.newArray(size);
639
+ iteratorVisitAll(new Visitor() {
640
+ public void visit(IRubyObject key, IRubyObject value) {
641
+ result.append(runtime.newArray(key, value));
642
+ }
643
+ });
644
+ return result;
645
+ }
646
+
647
+ @JRubyMethod
648
+ public IRubyObject flatten(final ThreadContext context, final Block block) {
649
+ RubyArray arg = to_a();
650
+ arg.callMethod(context, "flatten!", RubyFixnum.one(context.runtime));
651
+ return arg;
652
+ }
653
+
654
+ @JRubyMethod(name = "values_at", rest = true)
655
+ public IRubyObject values_at(final ThreadContext context, IRubyObject[] args) {
656
+ RubyArray result = RubyArray.newArray(context.runtime, args.length);
657
+ for (int i = 0; i < args.length; i++) {
658
+ result.append(op_aref(context, args[i]));
659
+ }
660
+ return result;
661
+ }
662
+
663
+ @JRubyMethod
664
+ public IRubyObject invert(final ThreadContext context) {
665
+ final MultiRBTree rbtree = (MultiRBTree) getMetaClass().getRealClass().allocate();
666
+ iteratorVisitAll(new Visitor() {
667
+ public void visit(IRubyObject key, IRubyObject value) {
668
+ rbtree.internalPut(context, value, key);
669
+ }
670
+ });
671
+ return rbtree;
672
+ }
673
+
674
+ private IRubyObject nodeOrDefault(ThreadContext context, Node node) {
675
+ return nodeOrDefault(context, node, false);
676
+ }
677
+
678
+ private IRubyObject nodeOrDefault(ThreadContext context, Node node, boolean deleteNode) {
679
+ if (node.isNull()) {
680
+ if (this.ifNone == null)
681
+ return getRuntime().getNil();
682
+ if ((flags & PROCDEFAULT_HASH_F) != 0)
683
+ return RuntimeHelpers.invoke(context, ifNone, "call", this, getRuntime().getNil());
684
+ return ifNone;
685
+ }
686
+ if (deleteNode) {
687
+ internalDelete(context, node);
688
+ }
689
+ return newArray(node);
690
+ }
691
+
692
+ @JRubyMethod(name = {"reverse_inorder_walk", "reverse_each"})
693
+ public IRubyObject reverse_each(final ThreadContext context, final Block block) {
694
+ return block.isGiven() ? reverse_eachInternal(context, block) : enumeratorize(context.runtime, this, "reverse_each");
695
+ }
696
+
697
+ private IRubyObject reverse_eachInternal(final ThreadContext context, final Block block) {
698
+ iteratorReverseVisitAll(new Visitor() {
699
+ public void visit(IRubyObject key, IRubyObject value) {
700
+ block.yieldSpecific(context, key, value);
701
+ }
702
+ });
703
+ return this;
704
+ }
705
+
706
+ public Node internalGet(ThreadContext context, RubyObject key) {
707
+ Node x = this.root;
708
+ while (!x.isNull()) {
709
+ int ret = compare(context, key, x.getKey());
710
+ if (ret > 0) {
711
+ x = x.getRight();
712
+ } else if (ret < 0) {
713
+ x = x.getLeft();
714
+ } else {
715
+ return x;
716
+ }
717
+ }
718
+ return null;
719
+ }
720
+
721
+ @JRubyMethod(name = "empty?")
722
+ public IRubyObject empty_p(ThreadContext context) {
723
+ return getRuntime().newBoolean(size == 0);
724
+ }
725
+
726
+ @JRubyMethod(name = "black_height")
727
+ public IRubyObject blackHeight() {
728
+ Node x = this.root;
729
+ int height = 0;
730
+ while (!x.isNull()) {
731
+ x = x.getLeft();
732
+ if (x.isNull() || x.isBlack()) height += 1;
733
+ }
734
+ return RubyFixnum.newFixnum(getRuntime(), height);
735
+ }
736
+
737
+ private void leftRotate(Node x) {
738
+ Node y = x.getRight();
739
+ x.setRight(y.getLeft());
740
+
741
+ if (!y.getLeft().isNull()) {
742
+ y.getLeft().setParent(x);
743
+ }
744
+ y.setParent(x.getParent());
745
+ if (x.getParent().isNull()) {
746
+ this.root = y;
747
+ } else {
748
+ if (x == x.getParent().getLeft()) {
749
+ x.getParent().setLeft(y);
750
+ } else {
751
+ x.getParent().setRight(y);
752
+ }
753
+ }
754
+ y.setLeft(x);
755
+ x.setParent(y);
756
+ }
757
+
758
+ private void rightRotate(Node x) {
759
+ Node y = x.getLeft();
760
+ x.setLeft(y.getRight());
761
+ if (! y.getRight().isNull()) {
762
+ y.getRight().setParent(x);
763
+ }
764
+ y.setParent(x.getParent());
765
+ if (x.getParent().isNull()) {
766
+ this.root = y;
767
+ } else {
768
+ if (x == x.getParent().getLeft()) {
769
+ x.getParent().setLeft(y);
770
+ } else {
771
+ x.getParent().setRight(y);
772
+ }
773
+ }
774
+ y.setRight(x);
775
+ x.setParent(y);
776
+ }
777
+ private int compare(ThreadContext context, Node a, Node b) {
778
+ return compare(context, a.getKey(), b.getKey());
779
+ }
780
+
781
+ private int compare(ThreadContext context, RubyObject a, RubyObject b) {
782
+ if (context == null || cmpProc == null)
783
+ return a.compareTo(b);
784
+ return (int) cmpProc.call(context, new IRubyObject[]{a, b}).convertToInteger().getLongValue();
785
+ }
786
+
787
+ private void internalPutHelper(ThreadContext context, Node z) {
788
+ Node y = NilNode.getInstance();
789
+ Node x = this.root;
790
+ while(!x.isNull()) {
791
+ y = x;
792
+ x = compare(context, z, x) < 0 ? x.getLeft() : x.getRight();
793
+ }
794
+ z.setParent(y);
795
+ if (y.isNull()) {
796
+ this.root = z;
797
+ } else {
798
+ if (compare(context, z, y) < 0) {
799
+ y.setLeft(z);
800
+ } else {
801
+ y.setRight(z);
802
+ }
803
+ }
804
+ this.size += 1;
805
+ }
806
+
807
+ private void deleteFixup(Node x) {
808
+ while (x != this.root && x.isBlack()) {
809
+ if (x.isLeft()) {
810
+ Node w = x.getParent().getRight();
811
+ if (w.isRed()) {
812
+ w.setBlack();
813
+ x.getParent().setRed();
814
+ leftRotate(x.getParent());
815
+ w = x.getParent().getRight();
816
+ }
817
+ if (w.getLeft().isBlack() && w.getRight().isBlack()) {
818
+ w.setRed();
819
+ x = x.getParent();
820
+ } else {
821
+ if (w.getRight().isBlack()) {
822
+ w.getLeft().setBlack();
823
+ w.setRed();
824
+ rightRotate(w);
825
+ w = x.getParent().getRight();
826
+ }
827
+ w.setColor(x.getParent().getColor());
828
+ x.getParent().setBlack();
829
+ w.getRight().setBlack();
830
+ leftRotate(x.getParent());
831
+ x = this.root;
832
+ }
833
+ } else {
834
+ Node w = x.getParent().getLeft();
835
+ if (w.isRed()) {
836
+ w.setBlack();
837
+ x.getParent().setRed();
838
+ rightRotate(x.getParent());
839
+ w = x.getParent().getLeft();
840
+ }
841
+ if (w.getRight().isBlack() && w.getLeft().isBlack()) {
842
+ w.setRed();
843
+ x = x.getParent();
844
+ } else {
845
+ if (w.getLeft().isBlack()) {
846
+ w.getRight().setBlack();
847
+ w.setRed();
848
+ rightRotate(w);
849
+ w = x.getParent().getLeft();
850
+ }
851
+ w.setColor(x.getParent().getColor());
852
+ x.getParent().setBlack();
853
+ w.getLeft().setBlack();
854
+ rightRotate(x.getParent());
855
+ x = this.root;
856
+ }
857
+ }
858
+ }
859
+ x.setBlack();
860
+ }
861
+
862
+ @JRubyMethod(name = "size")
863
+ public IRubyObject getSize() {
864
+ return getRuntime().newFixnum(this.size);
865
+ }
866
+
867
+ @JRubyMethod
868
+ public IRubyObject last(ThreadContext context) {
869
+ return nodeOrDefault(context, maximum());
870
+ }
871
+
872
+ @JRubyMethod
873
+ public IRubyObject first(ThreadContext context) {
874
+ return nodeOrDefault(context, minimum());
875
+ }
876
+
877
+ public Node lower_boundInternal(ThreadContext context, IRubyObject key) {
878
+ Ruby runtime = getRuntime();
879
+ Node node = this.root;
880
+ Node tentative = NilNode.getInstance();
881
+ while (!node.isNull()) {
882
+ int result = compare(context, (RubyObject) key, node.getKey());
883
+ if (result > 0) {
884
+ node = node.getRight();
885
+ } else if (result < 0) {
886
+ tentative = node;
887
+ node = node.getLeft();
888
+ } else {
889
+ if (!dupes) {
890
+ return node;
891
+ } else {
892
+ tentative = node;
893
+ node = node.getLeft();
894
+ }
895
+ }
896
+ }
897
+ return tentative;
898
+ }
899
+
900
+ @JRubyMethod
901
+ public IRubyObject lower_bound(ThreadContext context, IRubyObject key) {
902
+ Node node = lower_boundInternal(context, key);
903
+ return node.isNull() ? context.runtime.getNil() : newArray(node);
904
+ }
905
+
906
+ public Node upper_boundInternal(ThreadContext context, IRubyObject key) {
907
+ Ruby runtime = getRuntime();
908
+ Node node = this.root;
909
+ Node tentative = NilNode.getInstance();
910
+ while (!node.isNull()) {
911
+ int result = compare(context, (RubyObject) key, node.getKey());
912
+ if (result < 0) {
913
+ node = node.getLeft();
914
+ } else if (result > 0) {
915
+ tentative = node;
916
+ node = node.getRight();
917
+ } else {
918
+ if (!dupes) {
919
+ return node;
920
+ } else { // if there are duplicates, go to the far right
921
+ tentative = node;
922
+ node = node.getRight();
923
+ }
924
+ }
925
+ }
926
+ return tentative;
927
+ }
928
+
929
+ @JRubyMethod
930
+ public IRubyObject upper_bound(ThreadContext context, IRubyObject key) {
931
+ Node node = upper_boundInternal(context, key);
932
+ return node.isNull() ? context.runtime.getNil() : newArray(node);
933
+ }
934
+
935
+ @JRubyMethod(name = "bound", required = 1, optional = 1)
936
+ public IRubyObject bound(final ThreadContext context, final IRubyObject[] bounds, final Block block) {
937
+ final Ruby runtime = getRuntime();
938
+ final RubyArray ret = runtime.newArray();
939
+ iteratorVisitAll(new Visitor() {
940
+ public void visit(IRubyObject key, IRubyObject value) {
941
+ if (((RubyObject) key).compareTo((RubyObject) bounds[0]) == 0
942
+ || bounds.length == 2 && ((RubyObject) key).compareTo((RubyObject) bounds[0]) >= 0
943
+ && ((RubyObject) key).compareTo((RubyObject) bounds[1]) <= 0) {
944
+ if (block.isGiven()) {
945
+ block.yieldSpecific(context, key, value);
946
+ } else {
947
+ ret.add(runtime.newArray(key, value));
948
+ }
949
+ }
950
+ }
951
+ });
952
+ return ret;
953
+ }
954
+
955
+ private RubyArray newArray(Node node) {
956
+ return getRuntime().newArray(node.getKey(), node.getValue());
957
+ }
958
+
959
+ @JRubyMethod(name = {"replace", "initialize_copy"})
960
+ public IRubyObject replace(final ThreadContext context, IRubyObject other) {
961
+ checkCompatible(context.runtime, other);
962
+ MultiRBTree otherTree = (MultiRBTree) other;
963
+ return replaceInternal(context, otherTree, otherTree.cmpProc);
964
+ }
965
+
966
+ private IRubyObject replaceInternal(final ThreadContext context, MultiRBTree otherTree, RubyProc cmpfunc) {
967
+ init();
968
+ if (this == otherTree) return this;
969
+ this.ifNone = otherTree.ifNone;
970
+ this.flags = otherTree.flags;
971
+ this.cmpProc = cmpfunc;
972
+ otherTree.visitAll(new Visitor() {
973
+ public void visit(IRubyObject key, IRubyObject value) {
974
+ internalPut(context, key, value);
975
+ }
976
+ });
977
+ return this;
978
+ }
979
+
980
+ @JRubyMethod(name = "==")
981
+ public IRubyObject op_equal(IRubyObject other) {
982
+ Ruby runtime = getRuntime();
983
+ if (this == other)
984
+ return runtime.getTrue();
985
+ if (!(other instanceof MultiRBTree))
986
+ return runtime.getFalse();
987
+ return this.dict_eq((MultiRBTree) other) ? runtime.getTrue() : runtime.getFalse();
988
+ }
989
+
990
+ private boolean dict_eq(MultiRBTree other) {
991
+ if (this.size != other.size || ! similar(other))
992
+ return false;
993
+ for (Node node1 = minimum(), node2 = other.minimum();
994
+ !node1.isNull() && !node2.isNull();
995
+ node1 = successor(node1), node2 = other.successor(node2))
996
+ {
997
+ if (!node1.key.eql(node2.key) || !node1.value.eql(node2.value))
998
+ return false;
999
+ }
1000
+ return true;
1001
+ }
1002
+
1003
+ private boolean similar(MultiRBTree other) {
1004
+ return this.cmpProc == other.cmpProc;
1005
+ }
1006
+
1007
+ private byte comma_breakable(ThreadContext context, IRubyObject pp) {
1008
+ return (byte) ',';
1009
+ }
1010
+
1011
+ private IRubyObject inspectMultiRBTree(final ThreadContext context, final IRubyObject pp) {
1012
+ final RubyString str = RubyString.newStringLight(context.runtime, DEFAULT_INSPECT_STR_SIZE);
1013
+ str.cat(new byte[]{'#', '<'}).cat(getMetaClass().getRealClass().getName().getBytes()).cat(": {".getBytes());
1014
+ final boolean[] firstEntry = new boolean[1];
1015
+
1016
+ firstEntry[0] = true;
1017
+ final boolean is19 = context.runtime.is1_9();
1018
+ visitAll(new Visitor() {
1019
+ public void visit(IRubyObject key, IRubyObject value) {
1020
+ //if (!firstEntry[0]) str.cat((byte)',').cat((byte)' ');
1021
+ if (!firstEntry[0]) str.cat(comma_breakable(context, pp)).cat((byte)' ');
1022
+
1023
+ RubyString inspectedKey = inspect(context, key);
1024
+ RubyString inspectedValue = inspect(context, value);
1025
+
1026
+ if (is19) {
1027
+ str.cat19(inspectedKey);
1028
+ str.cat((byte)'=').cat((byte)'>');
1029
+ str.cat19(inspectedValue);
1030
+ } else {
1031
+ str.cat(inspectedKey);
1032
+ str.cat((byte)'=').cat((byte)'>');
1033
+ str.cat(inspectedValue);
1034
+ }
1035
+
1036
+ firstEntry[0] = false;
1037
+ }
1038
+ });
1039
+ str.cat((byte) '}').cat(comma_breakable(context, pp)).cat(" default=".getBytes());
1040
+ if (ifNone == null) {
1041
+ str.cat("nil".getBytes());
1042
+ } else {
1043
+ str.cat(inspect(context, ifNone));
1044
+ }
1045
+ str.cat(comma_breakable(context, pp)).cat(" cmp_proc=".getBytes());
1046
+ if (cmpProc == null) {
1047
+ str.cat("nil".getBytes());
1048
+ } else {
1049
+ str.cat(inspect(context, cmpProc));
1050
+ }
1051
+ str.cat((byte)'>');
1052
+ return str;
1053
+ }
1054
+
1055
+ @JRubyMethod(name = "inspect")
1056
+ public IRubyObject inspect(ThreadContext context) {
1057
+ if (getRuntime().isInspecting(this)) return getRuntime().newString("#<RBTree: ...>");
1058
+
1059
+ try {
1060
+ getRuntime().registerInspecting(this);
1061
+ return inspectMultiRBTree(context, null);
1062
+ } finally {
1063
+ getRuntime().unregisterInspecting(this);
1064
+ }
1065
+ }
1066
+
1067
+ @JRubyMethod(name = "to_s")
1068
+ public IRubyObject to_s(ThreadContext context) {
1069
+ Ruby runtime = context.runtime;
1070
+ if (runtime.isInspecting(this)) return runtime.newString("{...}");
1071
+ try {
1072
+ runtime.registerInspecting(this);
1073
+ return to_a().to_s();
1074
+ } finally {
1075
+ runtime.unregisterInspecting(this);
1076
+ }
1077
+ }
1078
+
1079
+ private AtomicInteger iteratorCount = new AtomicInteger(0);
1080
+ private void iteratorEntry() {
1081
+ iteratorCount.incrementAndGet();
1082
+ }
1083
+ private void iteratorExit() {
1084
+ iteratorCount.decrementAndGet();
1085
+ }
1086
+
1087
+ private void checkIterating() {
1088
+ if (iteratorCount.get() > 0) {
1089
+ throw getRuntime().newRuntimeError("can't add a new key into RBTree during iteration");
1090
+ }
1091
+ }
1092
+
1093
+ private void visitAll(Visitor visitor) {
1094
+ for (Node x = minimum(); !x.isNull(); x = successor(x)) {
1095
+ visitor.visit(x.getKey(), x.getValue());
1096
+ }
1097
+ }
1098
+
1099
+ public void iteratorReverseVisitAll(Visitor visitor){
1100
+ try {
1101
+ iteratorEntry();
1102
+ for (Node x = maximum(); !x.isNull(); x = predecessor(x)) {
1103
+ visitor.visit(x.getKey(), x.getValue());
1104
+ }
1105
+ } finally {
1106
+ iteratorExit();
1107
+ }
1108
+ }
1109
+
1110
+ public void iteratorVisitAll(Visitor visitor){
1111
+ try {
1112
+ iteratorEntry();
1113
+ for (Node x = minimum(); !x.isNull(); x = successor(x)) {
1114
+ visitor.visit(x.getKey(), x.getValue());
1115
+ }
1116
+ } finally {
1117
+ iteratorExit();
1118
+ }
1119
+ }
1120
+
1121
+ private static class VisitorIOException extends RuntimeException {
1122
+ VisitorIOException(Throwable cause) {
1123
+ super(cause);
1124
+ }
1125
+ }
1126
+
1127
+ private static final ObjectMarshal RBTREE_MARSHAL = new ObjectMarshal() {
1128
+ public void marshalTo(Ruby runtime, final Object obj, RubyClass recv, final MarshalStream output) throws IOException {
1129
+ MultiRBTree rbtree = (MultiRBTree) obj;
1130
+ if (rbtree.size == 0) throw runtime.newArgumentError("cannot dump empty tree");
1131
+ if (rbtree.cmpProc != null) throw runtime.newArgumentError("cannot dump rbtree with compare proc");
1132
+ output.registerLinkTarget(rbtree);
1133
+ output.writeInt(rbtree.size);
1134
+ try {
1135
+ rbtree.visitAll(new Visitor() {
1136
+ public void visit(IRubyObject key, IRubyObject value) {
1137
+ try {
1138
+ output.dumpObject(key);
1139
+ output.dumpObject(value);
1140
+ } catch (IOException e) {
1141
+ throw new VisitorIOException(e);
1142
+ }
1143
+ }
1144
+ });
1145
+ } catch (VisitorIOException e) {
1146
+ throw (IOException) e.getCause();
1147
+ }
1148
+ //if (!rbtree.ifNone != null) output.dumpObject(rbtree.ifNone);
1149
+ }
1150
+
1151
+ public Object unmarshalFrom(Ruby runtime, RubyClass type, UnmarshalStream input) throws IOException {
1152
+ MultiRBTree result = (MultiRBTree) type.allocate();
1153
+ input.registerLinkTarget(result);
1154
+ int size = input.unmarshalInt();
1155
+ for (int i = 0; i < size; i++) {
1156
+ result.internalPut(runtime.getCurrentContext(), input.unmarshalObject(), input.unmarshalObject());
1157
+ }
1158
+ //if (defaultValue) result.default_value_set(input.unmarshalObject());
1159
+ return result;
1160
+ }
1161
+ };
1162
+
1163
+ private static abstract class Visitor {
1164
+ public abstract void visit(IRubyObject key, IRubyObject value);
1165
+ }
1166
+
1167
+ private static class Found extends RuntimeException {
1168
+ @Override
1169
+ public synchronized Throwable fillInStackTrace() {
1170
+ return null;
1171
+ }
1172
+ }
1173
+
1174
+ private static final Found FOUND = new Found();
1175
+
1176
+ private static class FoundKey extends Found {
1177
+ public final IRubyObject key;
1178
+ FoundKey(IRubyObject key) {
1179
+ super();
1180
+ this.key = key;
1181
+ }
1182
+ }
1183
+ }