KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jruby > RubyRange


1 /***** BEGIN LICENSE BLOCK *****
2  * Version: CPL 1.0/GPL 2.0/LGPL 2.1
3  *
4  * The contents of this file are subject to the Common Public
5  * License Version 1.0 (the "License"); you may not use this file
6  * except in compliance with the License. You may obtain a copy of
7  * the License at http://www.eclipse.org/legal/cpl-v10.html
8  *
9  * Software distributed under the License is distributed on an "AS
10  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
11  * implied. See the License for the specific language governing
12  * rights and limitations under the License.
13  *
14  * Copyright (C) 2001 Chad Fowler <chadfowler@chadfowler.com>
15  * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
16  * Copyright (C) 2001 Ed Sinjiashvili <slorcim@users.sourceforge.net>
17  * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
18  * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
19  * Copyright (C) 2002-2006 Thomas E Enebo <enebo@acm.org>
20  * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
21  * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
22  * Copyright (C) 2005 Charles O Nutter <headius@headius.com>
23  * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
24  *
25  * Alternatively, the contents of this file may be used under the terms of
26  * either of the GNU General Public License Version 2 or later (the "GPL"),
27  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28  * in which case the provisions of the GPL or the LGPL are applicable instead
29  * of those above. If you wish to allow use of your version of this file only
30  * under the terms of either the GPL or the LGPL, and not to allow others to
31  * use your version of this file under the terms of the CPL, indicate your
32  * decision by deleting the provisions above and replace them with the notice
33  * and other provisions required by the GPL or the LGPL. If you do not delete
34  * the provisions above, a recipient may use your version of this file under
35  * the terms of any one of the CPL, the GPL or the LGPL.
36  ***** END LICENSE BLOCK *****/

37 package org.jruby;
38
39 import java.io.IOException JavaDoc;
40 import java.util.HashMap JavaDoc;
41 import java.util.Map JavaDoc;
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 /**
53  * @author jpetersen
54  */

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 JavaDoc obj, RubyClass type,
91                               MarshalStream marshalStream) throws IOException JavaDoc {
92             RubyRange range = (RubyRange)obj;
93             
94             // FIXME: This is a pretty inefficient way to do this, but we need child class
95
// ivars and begin/end together
96
Map JavaDoc iVars = new HashMap JavaDoc(range.getInstanceVariables());
97             
98             // add our "begin" and "end" instance vars to the collection
99
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 JavaDoc unmarshalFrom(Ruby runtime, RubyClass type,
107                                     UnmarshalStream unmarshalStream) throws IOException JavaDoc {
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         // We override Enumerable#member? since ranges in 1.8.1 are continuous.
149
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     /**
159      * Converts this Range to a pair of integers representing a start position
160      * and length. If either of the range's endpoints is negative, it is added to
161      * the <code>limit</code> parameter in an attempt to arrive at a position
162      * <i>p</i> such that <i>0&nbsp;&lt;=&nbsp;p&nbsp;&lt;=&nbsp;limit</i>. If
163      * <code>truncate</code> is true, the result will be adjusted, if possible, so
164      * that <i>begin&nbsp;+&nbsp;length&nbsp;&lt;=&nbsp;limit</i>. If <code>strict</code>
165      * is true, an exception will be raised if the range can't be converted as
166      * described above; otherwise it just returns <b>null</b>.
167      *
168      * @param limit the size of the object (e.g., a String or Array) that
169      * this range is being evaluated against.
170      * @param truncate if true, result must fit within the range <i>(0..limit)</i>.
171      * @param isStrict if true, raises an exception if the range can't be converted.
172      * @return a two-element array representing a start value and a length,
173      * or <b>null</b> if the conversion failed.
174      */

175     public long[] getBeginLength(long limit, boolean truncate, boolean isStrict) {
176         long beginLong = RubyNumeric.num2long(begin);
177         long endLong = RubyNumeric.num2long(end);
178         
179         // Apparent legend for MRI 'err' param to JRuby 'truncate' and 'isStrict':
180
// 0 => truncate && !strict
181
// 1 => !truncate && strict
182
// 2 => truncate && strict
183

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 JavaDoc 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 { // Support length for arbitrary classes
337
IRubyObject currentObject = begin;
338         String JavaDoc 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 JavaDoc 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 JavaDoc 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