1 37 package org.jruby; 38 39 import java.io.IOException ; 40 import java.util.HashMap ; 41 import java.util.Map ; 42 import org.jruby.exceptions.RaiseException; 43 import org.jruby.runtime.Block; 44 import org.jruby.runtime.CallbackFactory; 45 import org.jruby.runtime.ObjectAllocator; 46 import org.jruby.runtime.ObjectMarshal; 47 import org.jruby.runtime.ThreadContext; 48 import org.jruby.runtime.builtin.IRubyObject; 49 import org.jruby.runtime.marshal.MarshalStream; 50 import org.jruby.runtime.marshal.UnmarshalStream; 51 52 55 public class RubyRange extends RubyObject { 56 57 private IRubyObject begin; 58 private IRubyObject end; 59 private boolean isExclusive; 60 61 public RubyRange(Ruby runtime, RubyClass impl) { 62 super(runtime, impl); 63 } 64 65 public void init(IRubyObject aBegin, IRubyObject aEnd, RubyBoolean aIsExclusive) { 66 if (!(aBegin instanceof RubyFixnum && aEnd instanceof RubyFixnum)) { 67 try { 68 aBegin.callMethod(getRuntime().getCurrentContext(), "<=>", aEnd); 69 } catch (RaiseException rExcptn) { 70 throw getRuntime().newArgumentError("bad value for range"); 71 } 72 } 73 74 this.begin = aBegin; 75 this.end = aEnd; 76 this.isExclusive = aIsExclusive.isTrue(); 77 } 78 79 private static ObjectAllocator RANGE_ALLOCATOR = new ObjectAllocator() { 80 public IRubyObject allocate(Ruby runtime, RubyClass klass) { 81 return new RubyRange(runtime, klass); 82 } 83 }; 84 85 public IRubyObject doClone(){ 86 return RubyRange.newRange(getRuntime(), begin, end, isExclusive); 87 } 88 89 private static final ObjectMarshal RANGE_MARSHAL = new ObjectMarshal() { 90 public void marshalTo(Ruby runtime, Object obj, RubyClass type, 91 MarshalStream marshalStream) throws IOException { 92 RubyRange range = (RubyRange)obj; 93 94 Map iVars = new HashMap (range.getInstanceVariables()); 97 98 iVars.put("begin", range.begin); 100 iVars.put("end", range.end); 101 iVars.put("excl", range.isExclusive? runtime.getTrue() : runtime.getFalse()); 102 103 marshalStream.dumpInstanceVars(iVars); 104 } 105 106 public Object unmarshalFrom(Ruby runtime, RubyClass type, 107 UnmarshalStream unmarshalStream) throws IOException { 108 RubyRange range = (RubyRange)type.allocate(); 109 110 unmarshalStream.registerLinkTarget(range); 111 112 unmarshalStream.defaultInstanceVarsUnmarshal(range); 113 114 range.begin = range.getInstanceVariable("begin"); 115 range.end = range.getInstanceVariable("end"); 116 range.isExclusive = range.getInstanceVariable("excl").isTrue(); 117 118 return range; 119 } 120 }; 121 122 public static RubyClass createRangeClass(Ruby runtime) { 123 RubyClass result = runtime.defineClass("Range", runtime.getObject(), RANGE_ALLOCATOR); 124 125 result.setMarshal(RANGE_MARSHAL); 126 127 CallbackFactory callbackFactory = runtime.callbackFactory(RubyRange.class); 128 129 result.includeModule(runtime.getModule("Enumerable")); 130 131 result.defineMethod("==", callbackFactory.getMethod("equal", RubyKernel.IRUBY_OBJECT)); 132 result.defineFastMethod("begin", callbackFactory.getFastMethod("first")); 133 result.defineMethod("each", callbackFactory.getMethod("each")); 134 result.defineFastMethod("end", callbackFactory.getFastMethod("last")); 135 result.defineFastMethod("exclude_end?", callbackFactory.getFastMethod("exclude_end_p")); 136 result.defineFastMethod("first", callbackFactory.getFastMethod("first")); 137 result.defineFastMethod("hash", callbackFactory.getFastMethod("hash")); 138 result.defineMethod("initialize", callbackFactory.getOptMethod("initialize")); 139 result.defineMethod("inspect", callbackFactory.getMethod("inspect")); 140 result.defineFastMethod("last", callbackFactory.getFastMethod("last")); 141 result.defineMethod("length", callbackFactory.getMethod("length")); 142 result.defineMethod("size", callbackFactory.getMethod("length")); 143 result.defineMethod("step", callbackFactory.getOptMethod("step")); 144 result.defineMethod("to_s", callbackFactory.getMethod("to_s")); 145 146 result.defineMethod("to_a", callbackFactory.getMethod("to_a")); 147 result.defineMethod("include?", callbackFactory.getMethod("include_p", RubyKernel.IRUBY_OBJECT)); 148 result.defineAlias("member?", "include?"); 150 result.defineAlias("===", "include?"); 151 152 CallbackFactory classCB = runtime.callbackFactory(RubyClass.class); 153 result.getMetaClass().defineMethod("new", classCB.getOptMethod("newInstance")); 154 155 return result; 156 } 157 158 175 public long[] getBeginLength(long limit, boolean truncate, boolean isStrict) { 176 long beginLong = RubyNumeric.num2long(begin); 177 long endLong = RubyNumeric.num2long(end); 178 179 184 if (! isExclusive) { 185 endLong++; 186 } 187 188 if (beginLong < 0) { 189 beginLong += limit; 190 if (beginLong < 0) { 191 if (isStrict) { 192 throw getRuntime().newRangeError(inspect().toString() + " out of range."); 193 } 194 return null; 195 } 196 } 197 198 if (truncate && beginLong > limit) { 199 if (isStrict) { 200 throw getRuntime().newRangeError(inspect().toString() + " out of range."); 201 } 202 return null; 203 } 204 205 if (truncate && endLong > limit) { 206 endLong = limit; 207 } 208 209 if (endLong < 0 || (!isExclusive && endLong == 0)) { 210 endLong += limit; 211 if (endLong < 0) { 212 if (isStrict) { 213 throw getRuntime().newRangeError(inspect().toString() + " out of range."); 214 } 215 return null; 216 } 217 } 218 219 return new long[] { beginLong, Math.max(endLong - beginLong, 0L) }; 220 } 221 222 public long[] begLen(long len, int err){ 223 long beg = RubyNumeric.num2long(this.begin); 224 long end = RubyNumeric.num2long(this.end); 225 226 if(beg < 0){ 227 beg += len; 228 if(beg < 0){ 229 if(err != 0){ 230 throw getRuntime().newRangeError(beg + ".." + (isExclusive ? "." : "") + end + " out of range"); 231 } 232 return null; 233 } 234 } 235 236 if(err == 0 || err == 2){ 237 if(beg > len){ 238 if(err != 0){ 239 throw getRuntime().newRangeError(beg + ".." + (isExclusive ? "." : "") + end + " out of range"); 240 } 241 return null; 242 } 243 if(end > len){ 244 end = len; 245 } 246 } 247 if(end < 0){ 248 end += len; 249 } 250 if(!isExclusive){ 251 end++; 252 } 253 len = end - beg; 254 if(len < 0){ 255 len = 0; 256 } 257 258 return new long[]{beg, len}; 259 } 260 261 public static RubyRange newRange(Ruby runtime, IRubyObject begin, IRubyObject end, boolean isExclusive) { 262 RubyRange range = new RubyRange(runtime, runtime.getClass("Range")); 263 range.init(begin, end, isExclusive ? runtime.getTrue() : runtime.getFalse()); 264 return range; 265 } 266 267 public IRubyObject initialize(IRubyObject[] args, Block unusedBlock) { 268 if (args.length == 3) { 269 init(args[0], args[1], (RubyBoolean) args[2]); 270 } else if (args.length == 2) { 271 init(args[0], args[1], getRuntime().getFalse()); 272 } else { 273 throw getRuntime().newArgumentError("Wrong arguments. (anObject, anObject, aBoolean = false) expected"); 274 } 275 return getRuntime().getNil(); 276 } 277 278 public IRubyObject first() { 279 return begin; 280 } 281 282 public IRubyObject last() { 283 return end; 284 } 285 286 public RubyFixnum hash() { 287 ThreadContext context = getRuntime().getCurrentContext(); 288 long baseHash = (isExclusive ? 1 : 0); 289 long beginHash = ((RubyFixnum) begin.callMethod(context, "hash")).getLongValue(); 290 long endHash = ((RubyFixnum) end.callMethod(context, "hash")).getLongValue(); 291 292 long hash = baseHash; 293 hash = hash ^ (beginHash << 1); 294 hash = hash ^ (endHash << 9); 295 hash = hash ^ (baseHash << 24); 296 297 return getRuntime().newFixnum(hash); 298 } 299 300 private static byte[] DOTDOTDOT = "...".getBytes(); 301 private static byte[] DOTDOT = "..".getBytes(); 302 303 private IRubyObject asString(String stringMethod) { 304 ThreadContext context = getRuntime().getCurrentContext(); 305 RubyString begStr = (RubyString) begin.callMethod(context, stringMethod); 306 RubyString endStr = (RubyString) end.callMethod(context, stringMethod); 307 308 return begStr.cat(isExclusive ? DOTDOTDOT : DOTDOT).concat(endStr); 309 } 310 311 public IRubyObject inspect(Block block) { 312 return asString("inspect"); 313 } 314 315 public IRubyObject to_s(Block block) { 316 return asString("to_s"); 317 } 318 319 public RubyBoolean exclude_end_p() { 320 return getRuntime().newBoolean(isExclusive); 321 } 322 323 public RubyFixnum length(Block block) { 324 long size = 0; 325 ThreadContext context = getRuntime().getCurrentContext(); 326 327 if (begin.callMethod(context, ">", end).isTrue()) { 328 return getRuntime().newFixnum(0); 329 } 330 331 if (begin instanceof RubyFixnum && end instanceof RubyFixnum) { 332 size = ((RubyNumeric) end).getLongValue() - ((RubyNumeric) begin).getLongValue(); 333 if (!isExclusive) { 334 size++; 335 } 336 } else { IRubyObject currentObject = begin; 338 String compareMethod = isExclusive ? "<" : "<="; 339 340 while (currentObject.callMethod(context, compareMethod, end).isTrue()) { 341 size++; 342 if (currentObject.equals(end)) { 343 break; 344 } 345 currentObject = currentObject.callMethod(context, "succ"); 346 } 347 } 348 return getRuntime().newFixnum(size); 349 } 350 351 public IRubyObject equal(IRubyObject obj, Block block) { 352 if (!(obj instanceof RubyRange)) { 353 return getRuntime().getFalse(); 354 } 355 RubyRange otherRange = (RubyRange) obj; 356 boolean result = 357 begin.equals(otherRange.begin) && 358 end.equals(otherRange.end) && 359 isExclusive == otherRange.isExclusive; 360 return getRuntime().newBoolean(result); 361 } 362 363 public IRubyObject each(Block block) { 364 ThreadContext context = getRuntime().getCurrentContext(); 365 366 if (begin instanceof RubyFixnum && end instanceof RubyFixnum) { 367 long endLong = ((RubyNumeric) end).getLongValue(); 368 long i = ((RubyNumeric) begin).getLongValue(); 369 370 if (!isExclusive) { 371 endLong += 1; 372 } 373 374 for (; i < endLong; i++) { 375 context.yield(getRuntime().newFixnum(i), block); 376 } 377 } else if (begin instanceof RubyString) { 378 ((RubyString) begin).upto(end, isExclusive, block); 379 } else if (begin.isKindOf(getRuntime().getClass("Numeric"))) { 380 if (!isExclusive) { 381 end = end.callMethod(context, "+", RubyFixnum.one(getRuntime())); 382 } 383 while (begin.callMethod(context, "<", end).isTrue()) { 384 context.yield(begin, block); 385 begin = begin.callMethod(context, "+", RubyFixnum.one(getRuntime())); 386 } 387 } else { 388 IRubyObject v = begin; 389 390 if (isExclusive) { 391 while (v.callMethod(context, "<", end).isTrue()) { 392 if (v.equals(end)) { 393 break; 394 } 395 context.yield(v, block); 396 v = v.callMethod(context, "succ"); 397 } 398 } else { 399 while (v.callMethod(context, "<=", end).isTrue()) { 400 context.yield(v, block); 401 if (v.equals(end)) { 402 break; 403 } 404 v = v.callMethod(context, "succ"); 405 } 406 } 407 } 408 409 return this; 410 } 411 412 public IRubyObject step(IRubyObject[] args, Block block) { 413 checkArgumentCount(args, 0, 1); 414 415 IRubyObject currentObject = begin; 416 String compareMethod = isExclusive ? "<" : "<="; 417 int stepSize = (int) (args.length == 0 ? 1 : args[0].convertToInteger().getLongValue()); 418 419 if (stepSize <= 0) { 420 throw getRuntime().newArgumentError("step can't be negative"); 421 } 422 423 ThreadContext context = getRuntime().getCurrentContext(); 424 if (begin instanceof RubyNumeric && end instanceof RubyNumeric) { 425 RubyFixnum stepNum = getRuntime().newFixnum(stepSize); 426 while (currentObject.callMethod(context, compareMethod, end).isTrue()) { 427 context.yield(currentObject, block); 428 currentObject = currentObject.callMethod(context, "+", stepNum); 429 } 430 } else { 431 while (currentObject.callMethod(context, compareMethod, end).isTrue()) { 432 context.yield(currentObject, block); 433 434 for (int i = 0; i < stepSize; i++) { 435 currentObject = currentObject.callMethod(context, "succ"); 436 } 437 } 438 } 439 440 return this; 441 } 442 443 public RubyArray to_a(Block block) { 444 IRubyObject currentObject = begin; 445 String compareMethod = isExclusive ? "<" : "<="; 446 RubyArray array = getRuntime().newArray(); 447 ThreadContext context = getRuntime().getCurrentContext(); 448 449 while (currentObject.callMethod(context, compareMethod, end).isTrue()) { 450 array.append(currentObject); 451 452 if (currentObject.equals(end)) { 453 break; 454 } 455 456 currentObject = currentObject.callMethod(context, "succ"); 457 } 458 459 return array; 460 } 461 462 private boolean r_lt(IRubyObject a, IRubyObject b) { 463 IRubyObject r = a.callMethod(getRuntime().getCurrentContext(),"<=>",b); 464 if(r.isNil()) { 465 return false; 466 } 467 if(RubyComparable.cmpint(r,a,b) < 0) { 468 return true; 469 } 470 return false; 471 } 472 473 private boolean r_le(IRubyObject a, IRubyObject b) { 474 IRubyObject r = a.callMethod(getRuntime().getCurrentContext(),"<=>",b); 475 if(r.isNil()) { 476 return false; 477 } 478 if(RubyComparable.cmpint(r,a,b) <= 0) { 479 return true; 480 } 481 return false; 482 } 483 484 public RubyBoolean include_p(IRubyObject obj, Block block) { 485 if(r_le(begin,obj)) { 486 if(isExclusive) { 487 if(r_lt(obj,end)) { 488 return getRuntime().getTrue(); 489 } 490 } else { 491 if(r_le(obj,end)) { 492 return getRuntime().getTrue(); 493 } 494 } 495 } 496 return getRuntime().getFalse(); 497 } 498 } 499 | Popular Tags |