rbtree-jruby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }