source: branches/streams/abcl/src/org/armedbear/lisp/Bignum.java

Last change on this file was 14757, checked in by Mark Evenson, 10 years ago

Futher fix for EQUALP on numeric tower

This fixes the following case

(let ((h1 (make-hash-table :test 'equalp))

(h2 (make-hash-table :test 'equalp))
(h (make-hash-table :test 'equalp)))

(setf (gethash 1 h1) 2

(gethash 2 h2) 1
(gethash h1 h) h2
(gethash h2 h) h1)

h)

See <https://mailman.common-lisp.net/pipermail/armedbear-devel/2015-April/003452.html>.
See <http://abcl.org/trac/ticket/388>.

Thanks to Massimiliano Ghilardi.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 21.1 KB
Line 
1/*
2 * Bignum.java
3 *
4 * Copyright (C) 2003-2007 Peter Graves
5 * $Id: Bignum.java 14757 2015-04-11 07:44:42Z mevenson $
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20 *
21 * As a special exception, the copyright holders of this library give you
22 * permission to link this library with independent modules to produce an
23 * executable, regardless of the license terms of these independent
24 * modules, and to copy and distribute the resulting executable under
25 * terms of your choice, provided that you also meet, for each linked
26 * independent module, the terms and conditions of the license of that
27 * module.  An independent module is a module which is not derived from
28 * or based on this library.  If you modify this library, you may extend
29 * this exception to your version of the library, but you are not
30 * obligated to do so.  If you do not wish to do so, delete this
31 * exception statement from your version.
32 */
33
34package org.armedbear.lisp;
35
36import static org.armedbear.lisp.Lisp.*;
37
38import java.math.BigInteger;
39
40public final class Bignum extends LispInteger
41{
42  public final BigInteger value;
43
44  private static BigInteger MOST_NEGATIVE_FIXNUM =
45          BigInteger.valueOf(Integer.MIN_VALUE);
46  private static BigInteger MOST_POSITIVE_FIXNUM =
47          BigInteger.valueOf(Integer.MAX_VALUE);
48
49  public static LispInteger getInstance(long l) {
50      if (Integer.MIN_VALUE <= l && l <= Integer.MAX_VALUE)
51          return Fixnum.getInstance(l);
52      else
53          return new Bignum(l);
54  }
55
56  public static LispInteger getInstance(BigInteger n) {
57      if (MOST_NEGATIVE_FIXNUM.compareTo(n) < 0 ||
58              MOST_POSITIVE_FIXNUM.compareTo(n) > 0)
59          return new Bignum(n);
60      else
61          return Fixnum.getInstance(n.intValue());
62  }
63
64  public static LispInteger getInstance(String s, int radix) {
65      BigInteger value = new BigInteger(s, radix);
66
67      return Bignum.getInstance(value);
68  }
69
70  private Bignum(long l)
71  {
72    value = BigInteger.valueOf(l);
73  }
74
75  private Bignum(BigInteger n)
76  {
77    value = n;
78  }
79
80  @Override
81  public Object javaInstance()
82  {
83    return value;
84  }
85
86  @Override
87  public Object javaInstance(Class c) {
88    String cn = c.getName();
89    if (cn.equals("java.lang.Byte") || cn.equals("byte"))
90      return Byte.valueOf((byte)value.intValue());
91    if (cn.equals("java.lang.Short") || cn.equals("short"))
92      return Short.valueOf((short)value.intValue());
93    if (cn.equals("java.lang.Integer") || cn.equals("int"))
94      return Integer.valueOf(value.intValue());
95    if (cn.equals("java.lang.Long") || cn.equals("long"))
96      return Long.valueOf((long)value.longValue());
97    return javaInstance();
98  }
99
100
101  @Override
102  public LispObject typeOf()
103  {
104    if (value.signum() > 0)
105      return list(Symbol.INTEGER,
106                   new Bignum((long)Integer.MAX_VALUE + 1));
107    return Symbol.BIGNUM;
108  }
109
110  @Override
111  public LispObject classOf()
112  {
113    return BuiltInClass.BIGNUM;
114  }
115
116  @Override
117  public LispObject typep(LispObject type)
118  {
119    if (type instanceof Symbol)
120      {
121        if (type == Symbol.BIGNUM)
122          return T;
123        if (type == Symbol.INTEGER)
124          return T;
125        if (type == Symbol.RATIONAL)
126          return T;
127        if (type == Symbol.REAL)
128          return T;
129        if (type == Symbol.NUMBER)
130          return T;
131        if (type == Symbol.SIGNED_BYTE)
132          return T;
133        if (type == Symbol.UNSIGNED_BYTE)
134          return value.signum() >= 0 ? T : NIL;
135      }
136    else if (type instanceof LispClass)
137      {
138        if (type == BuiltInClass.BIGNUM)
139          return T;
140        if (type == BuiltInClass.INTEGER)
141          return T;
142        if (type == BuiltInClass.RATIONAL)
143          return T;
144        if (type == BuiltInClass.REAL)
145          return T;
146        if (type == BuiltInClass.NUMBER)
147          return T;
148      }
149    else if (type instanceof Cons)
150      {
151        if (type.equal(UNSIGNED_BYTE_8))
152          return NIL;
153        if (type.equal(UNSIGNED_BYTE_32))
154          {
155            if (minusp())
156              return NIL;
157            return isLessThan(UNSIGNED_BYTE_32_MAX_VALUE) ? T : NIL;
158          }
159      }
160    return super.typep(type);
161  }
162
163  @Override
164  public boolean numberp()
165  {
166    return true;
167  }
168
169  @Override
170  public boolean integerp()
171  {
172    return true;
173  }
174
175  @Override
176  public boolean rationalp()
177  {
178    return true;
179  }
180
181  @Override
182  public boolean realp()
183  {
184    return true;
185  }
186
187  @Override
188  public boolean eql(LispObject obj)
189  {
190    if (this == obj)
191      return true;
192    if (obj instanceof Bignum)
193      {
194        if (value.equals(((Bignum)obj).value))
195          return true;
196      }
197    return false;
198  }
199
200  @Override
201  public boolean equal(LispObject obj)
202  {
203    if (this == obj)
204      return true;
205    if (obj instanceof Bignum)
206      {
207        if (value.equals(((Bignum)obj).value))
208          return true;
209      }
210    return false;
211  }
212
213  @Override
214  public boolean equalp(LispObject obj)
215  {
216    if (obj != null && obj.numberp())
217      return isEqualTo(obj);
218    return false;
219  }
220
221  @Override
222  public LispObject ABS()
223  {
224    if (value.signum() >= 0)
225      return this;
226    return new Bignum(value.negate());
227  }
228
229  @Override
230  public LispObject NUMERATOR()
231  {
232    return this;
233  }
234
235  @Override
236  public LispObject DENOMINATOR()
237  {
238    return Fixnum.ONE;
239  }
240
241  @Override
242  public boolean evenp()
243  {
244    return !value.testBit(0);
245  }
246
247  @Override
248  public boolean oddp()
249  {
250    return value.testBit(0);
251  }
252
253  @Override
254  public boolean plusp()
255  {
256    return value.signum() > 0;
257  }
258
259  @Override
260  public boolean minusp()
261  {
262    return value.signum() < 0;
263  }
264
265  @Override
266  public boolean zerop()
267  {
268    return false;
269  }
270
271  @Override
272  public int intValue()
273  {
274    return value.intValue();
275  }
276
277  @Override
278  public long longValue()
279  {
280    return value.longValue();
281  }
282
283  @Override
284  public float floatValue()
285  {
286    float f = value.floatValue();
287    if (Float.isInfinite(f))
288      error(new TypeError("The value " + princToString() +
289                           " is too large to be converted to a single float."));
290    return f;
291  }
292
293  @Override
294  public double doubleValue()
295  {
296    double d = value.doubleValue();
297    if (Double.isInfinite(d))
298      error(new TypeError("The value " + princToString() +
299                           " is too large to be converted to a double float."));
300    return d;
301  }
302
303  public static BigInteger getValue(LispObject obj)
304  {
305         
306    if (obj instanceof Bignum)
307      {
308        return ((Bignum)obj).value;
309      }
310        type_error(obj, Symbol.BIGNUM);
311        // Not reached.
312        return null;
313  }
314
315  @Override
316  public final LispObject incr()
317  {
318    return number(value.add(BigInteger.ONE));
319  }
320
321  @Override
322  public final LispObject decr()
323  {
324    return number(value.subtract(BigInteger.ONE));
325  }
326
327  @Override
328  public LispObject add(int n)
329  {
330    return number(value.add(BigInteger.valueOf(n)));
331  }
332
333  @Override
334  public LispObject add(LispObject obj)
335  {
336    if (obj instanceof Fixnum)
337      return number(value.add(Fixnum.getBigInteger(obj)));
338    if (obj instanceof Bignum)
339      return number(value.add(((Bignum)obj).value));
340    if (obj instanceof Ratio)
341      {
342        BigInteger numerator = ((Ratio)obj).numerator();
343        BigInteger denominator = ((Ratio)obj).denominator();
344        return number(value.multiply(denominator).add(numerator),
345                      denominator);
346      }
347    if (obj instanceof SingleFloat)
348      return new SingleFloat(floatValue() + ((SingleFloat)obj).value);
349    if (obj instanceof DoubleFloat)
350      return new DoubleFloat(doubleValue() + ((DoubleFloat)obj).value);
351    if (obj instanceof Complex)
352      {
353        Complex c = (Complex) obj;
354        return Complex.getInstance(add(c.getRealPart()), c.getImaginaryPart());
355      }
356    return type_error(obj, Symbol.NUMBER);
357  }
358
359  @Override
360  public LispObject subtract(LispObject obj)
361  {
362    if (obj instanceof Fixnum)
363      return number(value.subtract(Fixnum.getBigInteger(obj)));
364    if (obj instanceof Bignum)
365      return number(value.subtract(((Bignum)obj).value));
366    if (obj instanceof Ratio)
367      {
368        BigInteger numerator = ((Ratio)obj).numerator();
369        BigInteger denominator = ((Ratio)obj).denominator();
370        return number(value.multiply(denominator).subtract(numerator),
371                      denominator);
372      }
373    if (obj instanceof SingleFloat)
374      return new SingleFloat(floatValue() - ((SingleFloat)obj).value);
375    if (obj instanceof DoubleFloat)
376      return new DoubleFloat(doubleValue() - ((DoubleFloat)obj).value);
377    if (obj instanceof Complex)
378      {
379        Complex c = (Complex) obj;
380        return Complex.getInstance(subtract(c.getRealPart()),
381                                   Fixnum.ZERO.subtract(c.getImaginaryPart()));
382      }
383    return type_error(obj, Symbol.NUMBER);
384  }
385
386  @Override
387  public LispObject multiplyBy(int n)
388  {
389    if (n == 0)
390      return Fixnum.ZERO;
391    if (n == 1)
392      return this;
393    return new Bignum(value.multiply(BigInteger.valueOf(n)));
394  }
395
396  @Override
397  public LispObject multiplyBy(LispObject obj)
398  {
399    if (obj instanceof Fixnum)
400      {
401        int n = ((Fixnum)obj).value;
402        if (n == 0)
403          return Fixnum.ZERO;
404        if (n == 1)
405          return this;
406        return new Bignum(value.multiply(BigInteger.valueOf(n)));
407      }
408    if (obj instanceof Bignum)
409      return new Bignum(value.multiply(((Bignum)obj).value));
410    if (obj instanceof Ratio)
411      {
412        BigInteger n = ((Ratio)obj).numerator();
413        return number(n.multiply(value), ((Ratio)obj).denominator());
414      }
415    if (obj instanceof SingleFloat)
416      return new SingleFloat(floatValue() * ((SingleFloat)obj).value);
417    if (obj instanceof DoubleFloat)
418      return new DoubleFloat(doubleValue() * ((DoubleFloat)obj).value);
419    if (obj instanceof Complex)
420      {
421        Complex c = (Complex) obj;
422        return Complex.getInstance(multiplyBy(c.getRealPart()),
423                                   multiplyBy(c.getImaginaryPart()));
424      }
425    return type_error(obj, Symbol.NUMBER);
426  }
427
428  @Override
429  public LispObject divideBy(LispObject obj)
430  {
431    if (obj instanceof Fixnum)
432      return number(value, Fixnum.getBigInteger(obj));
433    if (obj instanceof Bignum)
434      return number(value, ((Bignum)obj).value);
435    if (obj instanceof Ratio)
436      {
437        BigInteger d = ((Ratio)obj).denominator();
438        return number(d.multiply(value), ((Ratio)obj).numerator());
439      }
440    if (obj instanceof SingleFloat)
441      return new SingleFloat(floatValue() / ((SingleFloat)obj).value);
442    if (obj instanceof DoubleFloat)
443      return new DoubleFloat(doubleValue() / ((DoubleFloat)obj).value);
444    if (obj instanceof Complex)
445      {
446        Complex c = (Complex) obj;
447        LispObject realPart = c.getRealPart();
448        LispObject imagPart = c.getImaginaryPart();
449        LispObject denominator =
450          realPart.multiplyBy(realPart).add(imagPart.multiplyBy(imagPart));
451        return Complex.getInstance(multiplyBy(realPart).divideBy(denominator),
452                                   Fixnum.ZERO.subtract(multiplyBy(imagPart).divideBy(denominator)));
453      }
454    return type_error(obj, Symbol.NUMBER);
455  }
456
457  @Override
458  public boolean isEqualTo(LispObject obj)
459  {
460    if (obj instanceof Bignum)
461      return value.equals(((Bignum)obj).value);
462    if (obj instanceof SingleFloat)
463      return isEqualTo(((SingleFloat)obj).rational());
464    if (obj instanceof DoubleFloat)
465      return isEqualTo(((DoubleFloat)obj).rational());
466    if (obj.numberp())
467      return false;
468    type_error(obj, Symbol.NUMBER);
469    // Not reached.
470    return false;
471  }
472
473  @Override
474  public boolean isNotEqualTo(LispObject obj)
475  {
476    if (obj instanceof Bignum)
477      return !value.equals(((Bignum)obj).value);
478    if (obj instanceof SingleFloat)
479      return isNotEqualTo(((SingleFloat)obj).rational());
480    if (obj instanceof DoubleFloat)
481      return isNotEqualTo(((DoubleFloat)obj).rational());
482    if (obj.numberp())
483      return true;
484    type_error(obj, Symbol.NUMBER);
485    // Not reached.
486    return false;
487  }
488
489  @Override
490  public boolean isLessThan(LispObject obj)
491  {
492    if (obj instanceof Fixnum)
493      return value.compareTo(Fixnum.getBigInteger(obj)) < 0;
494    if (obj instanceof Bignum)
495      return value.compareTo(((Bignum)obj).value) < 0;
496    if (obj instanceof Ratio)
497      {
498        BigInteger n = value.multiply(((Ratio)obj).denominator());
499        return n.compareTo(((Ratio)obj).numerator()) < 0;
500      }
501    if (obj instanceof SingleFloat)
502      return isLessThan(((SingleFloat)obj).rational());
503    if (obj instanceof DoubleFloat)
504      return isLessThan(((DoubleFloat)obj).rational());
505    type_error(obj, Symbol.REAL);
506    // Not reached.
507    return false;
508  }
509
510  @Override
511  public boolean isGreaterThan(LispObject obj)
512  {
513    if (obj instanceof Fixnum)
514      return value.compareTo(Fixnum.getBigInteger(obj)) > 0;
515    if (obj instanceof Bignum)
516      return value.compareTo(((Bignum)obj).value) > 0;
517    if (obj instanceof Ratio)
518      {
519        BigInteger n = value.multiply(((Ratio)obj).denominator());
520        return n.compareTo(((Ratio)obj).numerator()) > 0;
521      }
522    if (obj instanceof SingleFloat)
523      return isGreaterThan(((SingleFloat)obj).rational());
524    if (obj instanceof DoubleFloat)
525      return isGreaterThan(((DoubleFloat)obj).rational());
526    type_error(obj, Symbol.REAL);
527    // Not reached.
528    return false;
529  }
530
531  @Override
532  public boolean isLessThanOrEqualTo(LispObject obj)
533  {
534    if (obj instanceof Fixnum)
535      return value.compareTo(Fixnum.getBigInteger(obj)) <= 0;
536    if (obj instanceof Bignum)
537      return value.compareTo(((Bignum)obj).value) <= 0;
538    if (obj instanceof Ratio)
539      {
540        BigInteger n = value.multiply(((Ratio)obj).denominator());
541        return n.compareTo(((Ratio)obj).numerator()) <= 0;
542      }
543    if (obj instanceof SingleFloat)
544      return isLessThanOrEqualTo(((SingleFloat)obj).rational());
545    if (obj instanceof DoubleFloat)
546      return isLessThanOrEqualTo(((DoubleFloat)obj).rational());
547    type_error(obj, Symbol.REAL);
548    // Not reached.
549    return false;
550  }
551
552  @Override
553  public boolean isGreaterThanOrEqualTo(LispObject obj)
554  {
555    if (obj instanceof Fixnum)
556      return value.compareTo(Fixnum.getBigInteger(obj)) >= 0;
557    if (obj instanceof Bignum)
558      return value.compareTo(((Bignum)obj).value) >= 0;
559    if (obj instanceof Ratio)
560      {
561        BigInteger n = value.multiply(((Ratio)obj).denominator());
562        return n.compareTo(((Ratio)obj).numerator()) >= 0;
563      }
564    if (obj instanceof SingleFloat)
565      return isGreaterThanOrEqualTo(((SingleFloat)obj).rational());
566    if (obj instanceof DoubleFloat)
567      return isGreaterThanOrEqualTo(((DoubleFloat)obj).rational());
568    type_error(obj, Symbol.REAL);
569    // Not reached.
570    return false;
571  }
572
573  @Override
574  public LispObject truncate(LispObject obj)
575  {
576    final LispThread thread = LispThread.currentThread();
577    LispObject value1, value2;
578    try
579      {
580        if (obj instanceof Fixnum)
581          {
582            BigInteger divisor = ((Fixnum)obj).getBigInteger();
583            BigInteger[] results = value.divideAndRemainder(divisor);
584            BigInteger quotient = results[0];
585            BigInteger remainder = results[1];
586            value1 = number(quotient);
587            value2 = (remainder.signum() == 0) ? Fixnum.ZERO : number(remainder);
588          }
589        else if (obj instanceof Bignum)
590          {
591            BigInteger divisor = ((Bignum)obj).value;
592            BigInteger[] results = value.divideAndRemainder(divisor);
593            BigInteger quotient = results[0];
594            BigInteger remainder = results[1];
595            value1 = number(quotient);
596            value2 = (remainder.signum() == 0) ? Fixnum.ZERO : number(remainder);
597          }
598        else if (obj instanceof Ratio)
599          {
600            Ratio divisor = (Ratio) obj;
601            LispObject quotient =
602              multiplyBy(divisor.DENOMINATOR()).truncate(divisor.NUMERATOR());
603            LispObject remainder =
604              subtract(quotient.multiplyBy(divisor));
605            value1 = quotient;
606            value2 = remainder;
607          }
608        else if (obj instanceof SingleFloat)
609          {
610            // "When rationals and floats are combined by a numerical
611            // function, the rational is first converted to a float of the
612            // same format." 12.1.4.1
613            return new SingleFloat(floatValue()).truncate(obj);
614          }
615        else if (obj instanceof DoubleFloat)
616          {
617            // "When rationals and floats are combined by a numerical
618            // function, the rational is first converted to a float of the
619            // same format." 12.1.4.1
620            return new DoubleFloat(doubleValue()).truncate(obj);
621          }
622        else
623          return type_error(obj, Symbol.REAL);
624      }
625    catch (ArithmeticException e)
626      {
627        if (obj.zerop())
628          return error(new DivisionByZero());
629        else
630          return error(new ArithmeticError(e.getMessage()));
631      }
632    return thread.setValues(value1, value2);
633  }
634
635  @Override
636  public LispObject ash(LispObject obj)
637  {
638    BigInteger n = value;
639    if (obj instanceof Fixnum)
640      {
641        int count = ((Fixnum)obj).value;
642        if (count == 0)
643          return this;
644        // BigInteger.shiftLeft() succumbs to a stack overflow if count
645        // is Integer.MIN_VALUE, so...
646        if (count == Integer.MIN_VALUE)
647          return n.signum() >= 0 ? Fixnum.ZERO : Fixnum.MINUS_ONE;
648        return number(n.shiftLeft(count));
649      }
650    if (obj instanceof Bignum)
651      {
652        BigInteger count = ((Bignum)obj).value;
653        if (count.signum() > 0)
654          return error(new LispError("Can't represent result of left shift."));
655        if (count.signum() < 0)
656          return n.signum() >= 0 ? Fixnum.ZERO : Fixnum.MINUS_ONE;
657        Debug.bug(); // Shouldn't happen.
658      }
659    return type_error(obj, Symbol.INTEGER);
660  }
661
662  @Override
663  public LispObject LOGNOT()
664  {
665    return number(value.not());
666  }
667
668  @Override
669  public LispObject LOGAND(int n)
670  {
671    if (n >= 0)
672      return Fixnum.getInstance(value.intValue() & n);
673    else
674      return number(value.and(BigInteger.valueOf(n)));
675  }
676
677  @Override
678  public LispObject LOGAND(LispObject obj)
679  {
680    if (obj instanceof Fixnum)
681      {
682        int n = ((Fixnum)obj).value;
683        if (n >= 0)
684          return Fixnum.getInstance(value.intValue() & n);
685        else
686          return number(value.and(BigInteger.valueOf(n)));
687      }
688    else if (obj instanceof Bignum)
689      {
690        final BigInteger n = ((Bignum)obj).value;
691        return number(value.and(n));
692      }
693    else
694      return type_error(obj, Symbol.INTEGER);
695  }
696
697  @Override
698  public LispObject LOGIOR(int n)
699  {
700    return number(value.or(BigInteger.valueOf(n)));
701  }
702
703  @Override
704  public LispObject LOGIOR(LispObject obj)
705  {
706    if (obj instanceof Fixnum)
707      {
708        final BigInteger n = ((Fixnum)obj).getBigInteger();
709        return number(value.or(n));
710      }
711    else if (obj instanceof Bignum)
712      {
713        final BigInteger n = ((Bignum)obj).value;
714        return number(value.or(n));
715      }
716    else
717      return type_error(obj, Symbol.INTEGER);
718  }
719
720  @Override
721  public LispObject LOGXOR(int n)
722  {
723    return number(value.xor(BigInteger.valueOf(n)));
724  }
725
726  @Override
727  public LispObject LOGXOR(LispObject obj)
728  {
729    final BigInteger n;
730    if (obj instanceof Fixnum)
731      n = ((Fixnum)obj).getBigInteger();
732    else if (obj instanceof Bignum)
733      n = ((Bignum)obj).value;
734    else
735      return type_error(obj, Symbol.INTEGER);
736    return number(value.xor(n));
737  }
738
739  @Override
740  public LispObject LDB(int size, int position)
741  {
742    BigInteger n = value.shiftRight(position);
743    BigInteger mask = BigInteger.ONE.shiftLeft(size).subtract(BigInteger.ONE);
744    return number(n.and(mask));
745  }
746
747  @Override
748  public int hashCode()
749  {
750    return value.hashCode();
751  }
752
753  @Override
754  public String printObject()
755  {
756    final LispThread thread = LispThread.currentThread();
757    final int base = Fixnum.getValue(Symbol.PRINT_BASE.symbolValue(thread));
758    String s = value.toString(base).toUpperCase();
759    if (Symbol.PRINT_RADIX.symbolValue(thread) != NIL)
760      {
761        StringBuffer sb = new StringBuffer();
762        switch (base)
763          {
764          case 2:
765            sb.append("#b");
766            sb.append(s);
767            break;
768          case 8:
769            sb.append("#o");
770            sb.append(s);
771            break;
772          case 10:
773            sb.append(s);
774            sb.append('.');
775            break;
776          case 16:
777            sb.append("#x");
778            sb.append(s);
779            break;
780          default:
781            sb.append('#');
782            sb.append(String.valueOf(base));
783            sb.append('r');
784            sb.append(s);
785            break;
786          }
787        s = sb.toString();
788      }
789    return s;
790  }
791}
Note: See TracBrowser for help on using the repository browser.