0001: /***** BEGIN LICENSE BLOCK *****
0002: * Version: CPL 1.0/GPL 2.0/LGPL 2.1
0003: *
0004: * The contents of this file are subject to the Common Public
0005: * License Version 1.0 (the "License"); you may not use this file
0006: * except in compliance with the License. You may obtain a copy of
0007: * the License at http://www.eclipse.org/legal/cpl-v10.html
0008: *
0009: * Software distributed under the License is distributed on an "AS
0010: * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
0011: * implied. See the License for the specific language governing
0012: * rights and limitations under the License.
0013: *
0014: * Copyright (C) 2007 William N Dortch <bill.dortch@gmail.com>
0015: *
0016: * Alternatively, the contents of this file may be used under the terms of
0017: * either of the GNU General Public License Version 2 or later (the "GPL"),
0018: * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
0019: * in which case the provisions of the GPL or the LGPL are applicable instead
0020: * of those above. If you wish to allow use of your version of this file only
0021: * under the terms of either the GPL or the LGPL, and not to allow others to
0022: * use your version of this file under the terms of the CPL, indicate your
0023: * decision by deleting the provisions above and replace them with the notice
0024: * and other provisions required by the GPL or the LGPL. If you do not delete
0025: * the provisions above, a recipient may use your version of this file under
0026: * the terms of any one of the CPL, the GPL or the LGPL.
0027: ***** END LICENSE BLOCK *****/package org.jruby.util;
0028:
0029: import java.math.BigInteger;
0030: import java.text.DecimalFormatSymbols;
0031: import java.util.List;
0032: import java.util.Locale;
0033:
0034: import org.jruby.Ruby;
0035: import org.jruby.RubyArray;
0036: import org.jruby.RubyBignum;
0037: import org.jruby.RubyFixnum;
0038: import org.jruby.RubyFloat;
0039: import org.jruby.RubyInteger;
0040: import org.jruby.RubyKernel;
0041: import org.jruby.RubyNumeric;
0042: import org.jruby.RubyString;
0043: import org.jruby.runtime.ClassIndex;
0044: import org.jruby.runtime.MethodIndex;
0045: import org.jruby.runtime.builtin.IRubyObject;
0046:
0047: /**
0048: * @author Bill Dortch
0049: *
0050: */
0051: public class Sprintf {
0052: private static final int FLAG_NONE = 0;
0053: private static final int FLAG_SPACE = 1 << 0;
0054: private static final int FLAG_ZERO = 1 << 1;
0055: private static final int FLAG_PLUS = 1 << 2;
0056: private static final int FLAG_MINUS = 1 << 3;
0057: private static final int FLAG_SHARP = 1 << 4;
0058: private static final int FLAG_WIDTH = 1 << 5;
0059: private static final int FLAG_PRECISION = 1 << 6;
0060:
0061: private static final byte[] PREFIX_OCTAL = { '0' };
0062: private static final byte[] PREFIX_HEX_LC = { '0', 'x' };
0063: private static final byte[] PREFIX_HEX_UC = { '0', 'X' };
0064: private static final byte[] PREFIX_BINARY_LC = { '0', 'b' };
0065: private static final byte[] PREFIX_BINARY_UC = { '0', 'B' };
0066:
0067: private static final byte[] PREFIX_NEGATIVE = { '.', '.' };
0068:
0069: private static final byte[] NAN_VALUE = { 'N', 'a', 'N' };
0070: private static final byte[] INFINITY_VALUE = { 'I', 'n', 'f' };
0071:
0072: private static final BigInteger BIG_32 = BigInteger
0073: .valueOf(((long) Integer.MAX_VALUE + 1L) << 1);
0074: private static final BigInteger BIG_64 = BIG_32.shiftLeft(32);
0075: private static final BigInteger BIG_MINUS_32 = BigInteger
0076: .valueOf((long) Integer.MIN_VALUE << 1);
0077: private static final BigInteger BIG_MINUS_64 = BIG_MINUS_32
0078: .shiftLeft(32);
0079:
0080: private static final int INITIAL_BUFFER_SIZE = 32;
0081:
0082: private static final String ERR_MALFORMED_FORMAT = "malformed format string";
0083: private static final String ERR_MALFORMED_NUM = "malformed format string - %[0-9]";
0084: private static final String ERR_MALFORMED_DOT_NUM = "malformed format string - %.[0-9]";
0085: private static final String ERR_MALFORMED_STAR_NUM = "malformed format string - %*[0-9]";
0086: private static final String ERR_ILLEGAL_FORMAT_CHAR = "illegal format character - %";
0087:
0088: private static class Args {
0089: Ruby runtime;
0090: Locale locale;
0091: IRubyObject rubyObject;
0092: List rubyArray;
0093: int length;
0094: int unnumbered; // last index (+1) accessed by next()
0095: int numbered; // last index (+1) accessed by get()
0096:
0097: Args(Locale locale, IRubyObject rubyObject) {
0098: if (rubyObject == null) {
0099: throw new IllegalArgumentException(
0100: "null IRubyObject passed to sprintf");
0101: }
0102: this .locale = locale == null ? Locale.getDefault() : locale;
0103: this .rubyObject = rubyObject;
0104: if (rubyObject instanceof RubyArray) {
0105: this .rubyArray = ((RubyArray) rubyObject).getList();
0106: this .length = rubyArray.size();
0107: } else {
0108: this .length = 1;
0109: }
0110: this .runtime = rubyObject.getRuntime();
0111: }
0112:
0113: Args(IRubyObject rubyObject) {
0114: this (Locale.getDefault(), rubyObject);
0115: }
0116:
0117: // temporary hack to handle non-Ruby values
0118: // will come up with better solution shortly
0119: Args(Ruby runtime, long value) {
0120: this (RubyFixnum.newFixnum(runtime, value));
0121: }
0122:
0123: final void raiseArgumentError(String message) {
0124: throw runtime.newArgumentError(message);
0125: }
0126:
0127: final void warn(String message) {
0128: runtime.getWarnings().warn(message);
0129: }
0130:
0131: final void warning(String message) {
0132: runtime.getWarnings().warning(message);
0133: }
0134:
0135: final IRubyObject next() {
0136: // this is the order in which MRI does these two tests
0137: if (numbered > 0) {
0138: raiseArgumentError("unnumbered" + (unnumbered + 1)
0139: + "mixed with numbered");
0140: }
0141: if (unnumbered >= length) {
0142: raiseArgumentError("too few arguments");
0143: }
0144:
0145: IRubyObject object = rubyArray == null ? rubyObject
0146: : (IRubyObject) rubyArray.get(unnumbered);
0147: unnumbered++;
0148: return object;
0149: }
0150:
0151: final IRubyObject get(int index) {
0152: // this is the order in which MRI does these tests
0153: if (unnumbered > 0) {
0154: raiseArgumentError("numbered(" + numbered
0155: + ") after unnumbered(" + unnumbered + ")");
0156: }
0157: if (index < 0) {
0158: raiseArgumentError("invalid index - " + (index + 1)
0159: + '$');
0160: }
0161: if (index >= length) {
0162: raiseArgumentError("too few arguments");
0163: }
0164:
0165: numbered = index + 1;
0166: return (rubyArray == null ? rubyObject
0167: : (IRubyObject) rubyArray.get(index));
0168: }
0169:
0170: final IRubyObject getNth(int formatIndex) {
0171: return get(formatIndex - 1);
0172: }
0173:
0174: final int nextInt() {
0175: return intValue(next());
0176: }
0177:
0178: final int getInt(int index) {
0179: return intValue(get(index));
0180: }
0181:
0182: final int getNthInt(int formatIndex) {
0183: return intValue(get(formatIndex - 1));
0184: }
0185:
0186: final int intValue(IRubyObject obj) {
0187: if (obj instanceof RubyNumeric) {
0188: return (int) ((RubyNumeric) obj).getLongValue();
0189: }
0190:
0191: // basically just forcing a TypeError here to match MRI
0192: obj = obj.convertToType(obj.getRuntime().getFixnum(),
0193: MethodIndex.TO_INT, "to_int", true);
0194: return (int) ((RubyFixnum) obj).getLongValue();
0195: }
0196:
0197: final byte getDecimalSeparator() {
0198: // not saving DFS instance, as it will only be used once (at most) per call
0199: return (byte) new DecimalFormatSymbols(locale)
0200: .getDecimalSeparator();
0201: }
0202: } // Args
0203:
0204: /*
0205: * Using this class to buffer output during formatting, rather than
0206: * the eventual ByteList itself. That way this buffer can be initialized
0207: * to a size large enough to prevent reallocations (in most cases), while
0208: * the resultant ByteList will only be as large as necessary.
0209: *
0210: * (Also, the Buffer class's buffer grows by a factor of 2, as opposed
0211: * to ByteList's 1.5, which I felt might result in a lot of reallocs.)
0212: */
0213: private static class Buffer {
0214: byte[] buf;
0215: int size;
0216:
0217: Buffer() {
0218: buf = new byte[INITIAL_BUFFER_SIZE];
0219: }
0220:
0221: Buffer(int initialSize) {
0222: buf = new byte[initialSize];
0223: }
0224:
0225: final void write(int b) {
0226: int newSize = size + 1;
0227: if (newSize > buf.length) {
0228: byte[] newBuf = new byte[Math.max(buf.length << 1,
0229: newSize)];
0230: System.arraycopy(buf, 0, newBuf, 0, size);
0231: buf = newBuf;
0232: }
0233: buf[size] = (byte) (b & 0xff);
0234: size = newSize;
0235: }
0236:
0237: final void write(byte[] b, int off, int len) {
0238: if (len <= 0 || off < 0)
0239: return;
0240:
0241: int newSize = size + len;
0242: if (newSize > buf.length) {
0243: byte[] newBuf = new byte[Math.max(buf.length << 1,
0244: newSize)];
0245: System.arraycopy(buf, 0, newBuf, 0, size);
0246: buf = newBuf;
0247: }
0248: System.arraycopy(b, off, buf, size, len);
0249: size = newSize;
0250: }
0251:
0252: final void write(byte[] b) {
0253: write(b, 0, b.length);
0254: }
0255:
0256: final void fill(int b, int len) {
0257: if (len <= 0)
0258: return;
0259:
0260: int newSize = size + len;
0261: if (newSize > buf.length) {
0262: byte[] newBuf = new byte[Math.max(buf.length << 1,
0263: newSize)];
0264: System.arraycopy(buf, 0, newBuf, 0, size);
0265: buf = newBuf;
0266: }
0267: byte fillval = (byte) (b & 0xff);
0268: for (; --len >= 0;) {
0269: buf[size + len] = fillval;
0270: }
0271: size = newSize;
0272: }
0273:
0274: final void set(int b, int pos) {
0275: if (pos < 0)
0276: pos += size;
0277: if (pos >= 0 && pos < size)
0278: buf[pos] = (byte) (b & 0xff);
0279: }
0280:
0281: // sets last char
0282: final void set(int b) {
0283: if (size > 0)
0284: buf[size - 1] = (byte) (b & 0xff);
0285: }
0286:
0287: final ByteList toByteList() {
0288: return new ByteList(buf, 0, size);
0289: }
0290:
0291: public final String toString() {
0292: return new String(buf, 0, size);
0293: }
0294: } // Buffer
0295:
0296: // static methods only
0297: private Sprintf() {
0298: }
0299:
0300: public static CharSequence sprintf(Locale locale,
0301: CharSequence format, IRubyObject args) {
0302: return rubySprintf(format, new Args(locale, args));
0303: }
0304:
0305: public static CharSequence sprintf(CharSequence format,
0306: IRubyObject args) {
0307: return rubySprintf(format, new Args(args));
0308: }
0309:
0310: public static CharSequence sprintf(Ruby runtime,
0311: CharSequence format, int arg) {
0312: return rubySprintf(format, new Args(runtime, (long) arg));
0313: }
0314:
0315: public static CharSequence sprintf(Ruby runtime,
0316: CharSequence format, long arg) {
0317: return rubySprintf(format, new Args(runtime, arg));
0318: }
0319:
0320: public static CharSequence sprintf(Locale locale,
0321: RubyString format, IRubyObject args) {
0322: return rubySprintf(format.getByteList(), new Args(locale, args));
0323: }
0324:
0325: public static CharSequence sprintf(RubyString format,
0326: IRubyObject args) {
0327: return rubySprintf(format.getByteList(), new Args(args));
0328: }
0329:
0330: private static CharSequence rubySprintf(CharSequence charFormat,
0331: Args args) {
0332: byte[] format;
0333: Buffer buf = new Buffer();
0334:
0335: int offset;
0336: int length;
0337: int start;
0338: int mark;
0339:
0340: if (charFormat instanceof ByteList) {
0341: ByteList list = (ByteList) charFormat;
0342: format = list.unsafeBytes();
0343: int begin = list.begin();
0344: offset = begin;
0345: length = begin + list.length();
0346: start = begin;
0347: mark = begin;
0348: } else {
0349: format = stringToBytes(charFormat, false);
0350: offset = 0;
0351: length = charFormat.length();
0352: start = 0;
0353: mark = 0;
0354: }
0355:
0356: while (offset < length) {
0357: start = offset;
0358: for (; offset < length && format[offset] != '%'; offset++)
0359: ;
0360: if (offset > start) {
0361: buf.write(format, start, offset - start);
0362: start = offset;
0363: }
0364: if (offset++ >= length)
0365: break;
0366:
0367: IRubyObject arg = null;
0368: int flags = 0;
0369: int width = 0;
0370: int precision = 0;
0371: int number = 0;
0372: byte fchar = 0;
0373: boolean incomplete = true;
0374: for (; incomplete && offset < length;) {
0375: switch (fchar = format[offset]) {
0376: default:
0377: if (isPrintable(fchar)) {
0378: raiseArgumentError(args,
0379: "malformed format string - %"
0380: + (char) fchar);
0381: } else {
0382: raiseArgumentError(args, ERR_MALFORMED_FORMAT);
0383: }
0384: break;
0385:
0386: case ' ':
0387: flags |= FLAG_SPACE;
0388: offset++;
0389: break;
0390: case '0':
0391: flags |= FLAG_ZERO;
0392: offset++;
0393: break;
0394: case '+':
0395: flags |= FLAG_PLUS;
0396: offset++;
0397: break;
0398: case '-':
0399: flags |= FLAG_MINUS;
0400: offset++;
0401: break;
0402: case '#':
0403: flags |= FLAG_SHARP;
0404: offset++;
0405: break;
0406: case '1':
0407: case '2':
0408: case '3':
0409: case '4':
0410: case '5':
0411: case '6':
0412: case '7':
0413: case '8':
0414: case '9':
0415: // MRI doesn't flag it as an error if width is given multiple
0416: // times as a number (but it does for *)
0417: number = 0;
0418: for (; offset < length
0419: && isDigit(fchar = format[offset]); offset++) {
0420: number = extendWidth(args, number, fchar);
0421: }
0422: checkOffset(args, offset, length, ERR_MALFORMED_NUM);
0423: if (fchar == '$') {
0424: if (arg != null) {
0425: raiseArgumentError(args,
0426: "value given twice - " + number
0427: + "$");
0428: }
0429: arg = args.getNth(number);
0430: offset++;
0431: } else {
0432: width = number;
0433: flags |= FLAG_WIDTH;
0434: }
0435: break;
0436:
0437: case '*':
0438: if ((flags & FLAG_WIDTH) != 0) {
0439: raiseArgumentError(args, "width given twice");
0440: }
0441: flags |= FLAG_WIDTH;
0442: // TODO: factor this chunk as in MRI/YARV GETASTER
0443: checkOffset(args, ++offset, length,
0444: ERR_MALFORMED_STAR_NUM);
0445: mark = offset;
0446: number = 0;
0447: for (; offset < length
0448: && isDigit(fchar = format[offset]); offset++) {
0449: number = extendWidth(args, number, fchar);
0450: }
0451: checkOffset(args, offset, length,
0452: ERR_MALFORMED_STAR_NUM);
0453: if (fchar == '$') {
0454: width = args.getNthInt(number);
0455: if (width < 0) {
0456: flags |= FLAG_MINUS;
0457: width = -width;
0458: }
0459: offset++;
0460: } else {
0461: width = args.nextInt();
0462: if (width < 0) {
0463: flags |= FLAG_MINUS;
0464: width = -width;
0465: }
0466: // let the width (if any), get processed in the next loop,
0467: // so any leading 0 gets treated correctly
0468: offset = mark;
0469: }
0470: break;
0471:
0472: case '.':
0473: if ((flags & FLAG_PRECISION) != 0) {
0474: raiseArgumentError(args,
0475: "precision given twice");
0476: }
0477: flags |= FLAG_PRECISION;
0478: checkOffset(args, ++offset, length,
0479: ERR_MALFORMED_DOT_NUM);
0480: fchar = format[offset];
0481: if (fchar == '*') {
0482: // TODO: factor this chunk as in MRI/YARV GETASTER
0483: checkOffset(args, ++offset, length,
0484: ERR_MALFORMED_STAR_NUM);
0485: mark = offset;
0486: number = 0;
0487: for (; offset < length
0488: && isDigit(fchar = format[offset]); offset++) {
0489: number = extendWidth(args, number, fchar);
0490: }
0491: checkOffset(args, offset, length,
0492: ERR_MALFORMED_STAR_NUM);
0493: if (fchar == '$') {
0494: precision = args.getNthInt(number);
0495: if (precision < 0) {
0496: flags &= ~FLAG_PRECISION;
0497: }
0498: offset++;
0499: } else {
0500: precision = args.nextInt();
0501: if (precision < 0) {
0502: flags &= ~FLAG_PRECISION;
0503: }
0504: // let the width (if any), get processed in the next loop,
0505: // so any leading 0 gets treated correctly
0506: offset = mark;
0507: }
0508: } else {
0509: number = 0;
0510: for (; offset < length
0511: && isDigit(fchar = format[offset]); offset++) {
0512: number = extendWidth(args, number, fchar);
0513: }
0514: checkOffset(args, offset, length,
0515: ERR_MALFORMED_DOT_NUM);
0516: precision = number;
0517: }
0518: break;
0519:
0520: case '\n':
0521: offset--;
0522: case '%':
0523: if (flags != FLAG_NONE) {
0524: raiseArgumentError(args,
0525: ERR_ILLEGAL_FORMAT_CHAR);
0526: }
0527: buf.write('%');
0528: offset++;
0529: incomplete = false;
0530: break;
0531:
0532: case 'c': {
0533: if (arg == null)
0534: arg = args.next();
0535:
0536: int c = 0;
0537: // MRI 1.8.5-p12 doesn't support 1-char strings, but
0538: // YARV 0.4.1 does. I don't think it hurts to include
0539: // this; sprintf('%c','a') is nicer than sprintf('%c','a'[0])
0540: if (arg instanceof RubyString) {
0541: ByteList bytes = ((RubyString) arg)
0542: .getByteList();
0543: if (bytes.length() == 1) {
0544: c = bytes.unsafeBytes()[bytes.begin()];
0545: } else {
0546: raiseArgumentError(args,
0547: "%c requires a character");
0548: }
0549: } else {
0550: c = args.intValue(arg);
0551: }
0552: if ((flags & FLAG_WIDTH) != 0 && width > 1) {
0553: if ((flags & FLAG_MINUS) != 0) {
0554: buf.write(c);
0555: buf.fill(' ', width - 1);
0556: } else {
0557: buf.fill(' ', width - 1);
0558: buf.write(c);
0559: }
0560: } else {
0561: buf.write(c);
0562: }
0563: offset++;
0564: incomplete = false;
0565: break;
0566: }
0567: case 'p':
0568: case 's': {
0569: if (arg == null)
0570: arg = args.next();
0571:
0572: if (fchar == 'p') {
0573: arg = arg.callMethod(arg.getRuntime()
0574: .getCurrentContext(), "inspect");
0575: }
0576: ByteList bytes = arg.asString().getByteList();
0577: int len = bytes.length();
0578: if ((flags & FLAG_PRECISION) != 0
0579: && precision < len) {
0580: len = precision;
0581: }
0582: // TODO: adjust length so it won't fall in the middle
0583: // of a multi-byte character. MRI's sprintf.c uses tables
0584: // in a modified version of regex.c, which assume some
0585: // particular encoding for a given installation/application.
0586: // (See regex.c#re_mbcinit in ruby-1.8.5-p12)
0587: //
0588: // This is only an issue if the user specifies a precision
0589: // that causes the string to be truncated. The same issue
0590: // would arise taking a substring of a ByteList-backed RubyString.
0591:
0592: if ((flags & FLAG_WIDTH) != 0 && width > len) {
0593: width -= len;
0594: if ((flags & FLAG_MINUS) != 0) {
0595: buf.write(bytes.unsafeBytes(), bytes
0596: .begin(), len);
0597: buf.fill(' ', width);
0598: } else {
0599: buf.fill(' ', width);
0600: buf.write(bytes.unsafeBytes(), bytes
0601: .begin(), len);
0602: }
0603: } else {
0604: buf.write(bytes.unsafeBytes(), bytes.begin(),
0605: len);
0606: }
0607: offset++;
0608: incomplete = false;
0609: break;
0610: }
0611: case 'd':
0612: case 'i':
0613: case 'o':
0614: case 'x':
0615: case 'X':
0616: case 'b':
0617: case 'B':
0618: case 'u': {
0619: if (arg == null)
0620: arg = args.next();
0621:
0622: int type = arg.getMetaClass().index;
0623: if (type != ClassIndex.FIXNUM
0624: && type != ClassIndex.BIGNUM) {
0625: switch (type) {
0626: case ClassIndex.FLOAT:
0627: arg = RubyNumeric.dbl2num(arg.getRuntime(),
0628: ((RubyFloat) arg).getValue());
0629: break;
0630: case ClassIndex.STRING:
0631: arg = RubyNumeric.str2inum(
0632: arg.getRuntime(), (RubyString) arg,
0633: 0, true);
0634: break;
0635: default:
0636: arg = arg.convertToType(arg.getRuntime()
0637: .getClass("Integer"),
0638: MethodIndex.TO_I, "to_i", true);
0639: break;
0640: }
0641: type = arg.getMetaClass().index;
0642: }
0643: byte[] bytes = null;
0644: int first = 0;
0645: byte[] prefix = null;
0646: boolean sign;
0647: boolean negative;
0648: byte signChar = 0;
0649: byte leadChar = 0;
0650: int base;
0651:
0652: // 'd' and 'i' are the same
0653: if (fchar == 'i')
0654: fchar = 'd';
0655:
0656: // 'u' with space or plus flags is same as 'd'
0657: if (fchar == 'u'
0658: && (flags & (FLAG_SPACE | FLAG_PLUS)) != 0) {
0659: fchar = 'd';
0660: }
0661: sign = (fchar == 'd' || (flags & (FLAG_SPACE | FLAG_PLUS)) != 0);
0662:
0663: switch (fchar) {
0664: case 'o':
0665: base = 8;
0666: break;
0667: case 'x':
0668: case 'X':
0669: base = 16;
0670: break;
0671: case 'b':
0672: case 'B':
0673: base = 2;
0674: break;
0675: case 'u':
0676: case 'd':
0677: default:
0678: base = 10;
0679: break;
0680: }
0681: if ((flags & FLAG_SHARP) != 0) {
0682: switch (fchar) {
0683: case 'o':
0684: prefix = PREFIX_OCTAL;
0685: break;
0686: case 'x':
0687: prefix = PREFIX_HEX_LC;
0688: break;
0689: case 'X':
0690: prefix = PREFIX_HEX_UC;
0691: break;
0692: case 'b':
0693: prefix = PREFIX_BINARY_LC;
0694: break;
0695: case 'B':
0696: prefix = PREFIX_BINARY_UC;
0697: break;
0698: }
0699: if (prefix != null)
0700: width -= prefix.length;
0701: }
0702: // We depart here from strict adherence to MRI code, as MRI
0703: // uses C-sprintf, in part, to format numeric output, while
0704: // we'll use Java's numeric formatting code (and our own).
0705: if (type == ClassIndex.FIXNUM) {
0706: negative = ((RubyFixnum) arg).getLongValue() < 0;
0707: if (negative && fchar == 'u') {
0708: bytes = getUnsignedNegativeBytes((RubyFixnum) arg);
0709: } else {
0710: bytes = getFixnumBytes((RubyFixnum) arg,
0711: base, sign, fchar == 'X');
0712: }
0713: } else {
0714: negative = ((RubyBignum) arg).getValue()
0715: .signum() < 0;
0716: if (negative && fchar == 'u') {
0717: bytes = getUnsignedNegativeBytes((RubyBignum) arg);
0718: } else {
0719: bytes = getBignumBytes((RubyBignum) arg,
0720: base, sign, fchar == 'X');
0721: }
0722: }
0723: int len = 0;
0724: if (sign) {
0725: if (negative) {
0726: signChar = '-';
0727: width--;
0728: first = 1; // skip '-' in bytes, will add where appropriate
0729: } else if ((flags & FLAG_PLUS) != 0) {
0730: signChar = '+';
0731: width--;
0732: } else if ((flags & FLAG_SPACE) != 0) {
0733: signChar = ' ';
0734: width--;
0735: }
0736: } else if (negative) {
0737: if (base == 10) {
0738: warning(args,
0739: "negative number for %u specifier");
0740: leadChar = '.';
0741: len += 2;
0742: } else {
0743: if ((flags & (FLAG_PRECISION | FLAG_ZERO)) == 0)
0744: len += 2; // ..
0745:
0746: first = skipSignBits(bytes, base);
0747: switch (fchar) {
0748: case 'b':
0749: case 'B':
0750: leadChar = '1';
0751: break;
0752: case 'o':
0753: leadChar = '7';
0754: break;
0755: case 'x':
0756: leadChar = 'f';
0757: break;
0758: case 'X':
0759: leadChar = 'F';
0760: break;
0761: }
0762: if (leadChar != 0)
0763: len++;
0764: }
0765: }
0766: int numlen = bytes.length - first;
0767: len += numlen;
0768:
0769: if ((flags & (FLAG_ZERO | FLAG_PRECISION)) == FLAG_ZERO) {
0770: precision = width;
0771: width = 0;
0772: } else {
0773: if (precision < len)
0774: precision = len;
0775:
0776: width -= precision;
0777: }
0778: if ((flags & FLAG_MINUS) == 0) {
0779: buf.fill(' ', width);
0780: width = 0;
0781: }
0782: if (signChar != 0)
0783: buf.write(signChar);
0784: if (prefix != null)
0785: buf.write(prefix);
0786:
0787: if (len < precision) {
0788: if (leadChar == 0) {
0789: buf.fill('0', precision - len);
0790: } else if (leadChar == '.') {
0791: buf.fill(leadChar, precision - len);
0792: buf.write(PREFIX_NEGATIVE);
0793: } else {
0794: buf.fill(leadChar, precision - len + 1); // the 1 is for the stripped sign char
0795: }
0796: } else if (leadChar != 0) {
0797: if ((flags & (FLAG_PRECISION | FLAG_ZERO)) == 0) {
0798: buf.write(PREFIX_NEGATIVE);
0799: }
0800: if (leadChar != '.')
0801: buf.write(leadChar);
0802: }
0803: buf.write(bytes, first, numlen);
0804:
0805: if (width > 0)
0806: buf.fill(' ', width);
0807:
0808: offset++;
0809: incomplete = false;
0810: break;
0811: }
0812: case 'E':
0813: case 'e':
0814: case 'f':
0815: case 'G':
0816: case 'g': {
0817: if (arg == null)
0818: arg = args.next();
0819:
0820: if (!(arg instanceof RubyFloat)) {
0821: // FIXME: what is correct 'recv' argument?
0822: // (this does produce the desired behavior)
0823: arg = RubyKernel.new_float(arg, arg);
0824: }
0825: double dval = ((RubyFloat) arg).getDoubleValue();
0826: boolean nan = dval != dval;
0827: boolean inf = dval == Double.POSITIVE_INFINITY
0828: || dval == Double.NEGATIVE_INFINITY;
0829: boolean negative = dval < 0.0d;
0830: byte[] digits;
0831: int nDigits = 0;
0832: int exponent = 0;
0833:
0834: int len = 0;
0835: byte signChar;
0836:
0837: if (nan || inf) {
0838: if (nan) {
0839: digits = NAN_VALUE;
0840: len = NAN_VALUE.length;
0841: } else {
0842: digits = INFINITY_VALUE;
0843: len = INFINITY_VALUE.length;
0844: }
0845: if (negative) {
0846: signChar = '-';
0847: width--;
0848: } else if ((flags & FLAG_PLUS) != 0) {
0849: signChar = '+';
0850: width--;
0851: } else if ((flags & FLAG_SPACE) != 0) {
0852: signChar = ' ';
0853: width--;
0854: } else {
0855: signChar = 0;
0856: }
0857: width -= len;
0858:
0859: if (width > 0
0860: && (flags & (FLAG_ZERO | FLAG_MINUS)) == 0) {
0861: buf.fill(' ', width);
0862: width = 0;
0863: }
0864: if (signChar != 0)
0865: buf.write(signChar);
0866:
0867: if (width > 0 && (flags & FLAG_MINUS) == 0) {
0868: buf.fill('0', width);
0869: width = 0;
0870: }
0871: buf.write(digits);
0872: if (width > 0)
0873: buf.fill(' ', width);
0874:
0875: offset++;
0876: incomplete = false;
0877: break;
0878: }
0879: String str = Double.toString(dval);
0880: // grrr, arghh, want to subclass sun.misc.FloatingDecimal, but can't,
0881: // so we must do all this (the next 70 lines of code), which has already
0882: // been done by FloatingDecimal.
0883: int strlen = str.length();
0884: digits = new byte[strlen];
0885: int nTrailingZeroes = 0;
0886: int i = negative ? 1 : 0;
0887: int decPos = 0;
0888: byte ival;
0889: int_loop: for (; i < strlen;) {
0890: switch (ival = (byte) str.charAt(i++)) {
0891: case '0':
0892: if (nDigits > 0)
0893: nTrailingZeroes++;
0894:
0895: break; // switch
0896: case '1':
0897: case '2':
0898: case '3':
0899: case '4':
0900: case '5':
0901: case '6':
0902: case '7':
0903: case '8':
0904: case '9':
0905: if (nTrailingZeroes > 0) {
0906: for (; nTrailingZeroes > 0; nTrailingZeroes--) {
0907: digits[nDigits++] = '0';
0908: }
0909: }
0910: digits[nDigits++] = ival;
0911: break; // switch
0912: case '.':
0913: break int_loop;
0914: }
0915: }
0916: decPos = nDigits + nTrailingZeroes;
0917: dec_loop: for (; i < strlen;) {
0918: switch (ival = (byte) str.charAt(i++)) {
0919: case '0':
0920: if (nDigits > 0) {
0921: nTrailingZeroes++;
0922: } else {
0923: exponent--;
0924: }
0925: break; // switch
0926: case '1':
0927: case '2':
0928: case '3':
0929: case '4':
0930: case '5':
0931: case '6':
0932: case '7':
0933: case '8':
0934: case '9':
0935: if (nTrailingZeroes > 0) {
0936: for (; nTrailingZeroes > 0; nTrailingZeroes--) {
0937: digits[nDigits++] = '0';
0938: }
0939: }
0940: digits[nDigits++] = ival;
0941: break; // switch
0942: case 'E':
0943: break dec_loop;
0944: }
0945: }
0946: if (i < strlen) {
0947: int expSign;
0948: int expVal = 0;
0949: if (str.charAt(i) == '-') {
0950: expSign = -1;
0951: i++;
0952: } else {
0953: expSign = 1;
0954: }
0955: for (; i < strlen;) {
0956: expVal = expVal
0957: * 10
0958: + ((int) str.charAt(i++) - (int) '0');
0959: }
0960: exponent += expVal * expSign;
0961: }
0962: exponent += decPos - nDigits;
0963:
0964: // gotta have at least a zero...
0965: if (nDigits == 0) {
0966: digits[0] = '0';
0967: nDigits = 1;
0968: exponent = 0;
0969: }
0970:
0971: // OK, we now have the significand in digits[0...nDigits]
0972: // and the exponent in exponent. We're ready to format.
0973:
0974: int intDigits, intZeroes, intLength;
0975: int decDigits, decZeroes, decLength;
0976: byte expChar;
0977:
0978: if (negative) {
0979: signChar = '-';
0980: width--;
0981: } else if ((flags & FLAG_PLUS) != 0) {
0982: signChar = '+';
0983: width--;
0984: } else if ((flags & FLAG_SPACE) != 0) {
0985: signChar = ' ';
0986: width--;
0987: } else {
0988: signChar = 0;
0989: }
0990: if ((flags & FLAG_PRECISION) == 0) {
0991: precision = 6;
0992: }
0993:
0994: switch (fchar) {
0995: case 'E':
0996: case 'G':
0997: expChar = 'E';
0998: break;
0999: case 'e':
1000: case 'g':
1001: expChar = 'e';
1002: break;
1003: default:
1004: expChar = 0;
1005: }
1006:
1007: switch (fchar) {
1008: case 'g':
1009: case 'G':
1010: // an empirically derived rule: precision applies to
1011: // significand length, irrespective of exponent
1012:
1013: // an official rule, clarified: if the exponent
1014: // <clarif>after adjusting for exponent form</clarif>
1015: // is < -4, or the exponent <clarif>after adjusting
1016: // for exponent form</clarif> is greater than the
1017: // precision, use exponent form
1018: boolean expForm = (exponent + nDigits - 1 < -4 || exponent
1019: + nDigits > (precision == 0 ? 1
1020: : precision));
1021: // it would be nice (and logical!) if exponent form
1022: // behaved like E/e, and decimal form behaved like f,
1023: // but no such luck. hence:
1024: if (expForm) {
1025: // intDigits isn't used here, but if it were, it would be 1
1026: /* intDigits = 1; */
1027: decDigits = nDigits - 1;
1028: // precision for G/g includes integer digits
1029: precision = Math.max(0, precision - 1);
1030:
1031: if (precision < decDigits) {
1032: int n = round(digits, nDigits,
1033: precision, precision != 0);
1034: if (n > nDigits) {
1035: nDigits = n;
1036: }
1037: decDigits = Math.min(nDigits - 1,
1038: precision);
1039: }
1040: exponent += nDigits - 1;
1041: if (precision > 0) {
1042: len += 1 + precision; // n.prec
1043: } else {
1044: len += 1; // n
1045: if ((flags & FLAG_SHARP) != 0) {
1046: len++; // will have a trailing '.'
1047: }
1048: }
1049:
1050: width -= len + 5; // 5 -> e+nnn / e-nnn
1051:
1052: if (width > 0
1053: && (flags & (FLAG_ZERO | FLAG_MINUS)) == 0) {
1054: buf.fill(' ', width);
1055: width = 0;
1056: }
1057: if (signChar != 0) {
1058: buf.write(signChar);
1059: }
1060: if (width > 0 && (flags & FLAG_MINUS) == 0) {
1061: buf.fill('0', width);
1062: width = 0;
1063: }
1064: // now some data...
1065: buf.write(digits[0]);
1066: if (precision > 0) {
1067: buf.write(args.getDecimalSeparator()); // '.'
1068: if (decDigits > 0) {
1069: buf.write(digits, 1, decDigits);
1070: precision -= decDigits;
1071: }
1072: } else if ((flags & FLAG_SHARP) != 0) {
1073: buf.write(args.getDecimalSeparator());
1074: }
1075: buf.write(expChar); // E or e
1076: buf.write(exponent >= 0 ? '+' : '-');
1077: if (exponent < 0) {
1078: exponent = -exponent;
1079: }
1080: buf.write(exponent / 100 + '0');
1081: buf.write(exponent % 100 / 10 + '0');
1082: buf.write(exponent % 10 + '0');
1083: if (width > 0) {
1084: buf.fill(' ', width);
1085: }
1086: } else { // decimal form, like (but not *just* like!) 'f'
1087: intDigits = Math.max(0, Math.min(nDigits
1088: + exponent, nDigits));
1089: intZeroes = Math.max(0, exponent);
1090: intLength = intDigits + intZeroes;
1091: decDigits = nDigits - intDigits;
1092: decZeroes = Math.max(0,
1093: -(decDigits + exponent));
1094: decLength = decZeroes + decDigits;
1095: precision = Math.max(0, precision
1096: - intLength);
1097:
1098: if (precision < decDigits) {
1099: int n = round(digits, nDigits,
1100: intDigits + precision - 1,
1101: precision != 0);
1102: if (n > nDigits) {
1103: // digits array shifted, update all
1104: nDigits = n;
1105: intDigits = Math.max(0, Math
1106: .min(nDigits + exponent,
1107: nDigits));
1108: intLength = intDigits + intZeroes;
1109: decDigits = nDigits - intDigits;
1110: decZeroes = Math.max(0,
1111: -(decDigits + exponent));
1112: precision = Math.max(0,
1113: precision - 1);
1114: }
1115: decDigits = precision;
1116: decLength = decZeroes + decDigits;
1117: }
1118: len += intLength;
1119: if (decLength > 0) {
1120: len += decLength + 1;
1121: } else {
1122: if ((flags & FLAG_SHARP) != 0) {
1123: len++; // will have a trailing '.'
1124: if (precision > 0) { // g fills trailing zeroes if #
1125: len += precision;
1126: }
1127: }
1128: }
1129:
1130: width -= len;
1131:
1132: if (width > 0
1133: && (flags & (FLAG_ZERO | FLAG_MINUS)) == 0) {
1134: buf.fill(' ', width);
1135: width = 0;
1136: }
1137: if (signChar != 0) {
1138: buf.write(signChar);
1139: }
1140: if (width > 0 && (flags & FLAG_MINUS) == 0) {
1141: buf.fill('0', width);
1142: width = 0;
1143: }
1144: // now some data...
1145: if (intLength > 0) {
1146: if (intDigits > 0) { // s/b true, since intLength > 0
1147: buf.write(digits, 0, intDigits);
1148: }
1149: if (intZeroes > 0) {
1150: buf.fill('0', intZeroes);
1151: }
1152: } else {
1153: // always need at least a 0
1154: buf.write('0');
1155: }
1156: if (decLength > 0
1157: || (flags & FLAG_SHARP) != 0) {
1158: buf.write(args.getDecimalSeparator());
1159: }
1160: if (decLength > 0) {
1161: if (decZeroes > 0) {
1162: buf.fill('0', decZeroes);
1163: precision -= decZeroes;
1164: }
1165: if (decDigits > 0) {
1166: buf.write(digits, intDigits,
1167: decDigits);
1168: precision -= decDigits;
1169: }
1170: if ((flags & FLAG_SHARP) != 0
1171: && precision > 0) {
1172: buf.fill('0', precision);
1173: }
1174: }
1175: if ((flags & FLAG_SHARP) != 0
1176: && precision > 0) {
1177: buf.fill('0', precision);
1178: }
1179: if (width > 0) {
1180: buf.fill(' ', width);
1181: }
1182: }
1183: break;
1184:
1185: case 'f':
1186: intDigits = Math.max(0, Math.min(nDigits
1187: + exponent, nDigits));
1188: intZeroes = Math.max(0, exponent);
1189: intLength = intDigits + intZeroes;
1190: decDigits = nDigits - intDigits;
1191: decZeroes = Math
1192: .max(0, -(decDigits + exponent));
1193: decLength = decZeroes + decDigits;
1194:
1195: if (precision < decLength) {
1196: if (precision < decZeroes) {
1197: decDigits = 0;
1198: decZeroes = precision;
1199: } else {
1200: int n = round(digits, nDigits,
1201: intDigits + precision
1202: - decZeroes - 1,
1203: precision != 0);
1204: if (n > nDigits) {
1205: // digits arr shifted, update all
1206: nDigits = n;
1207: intDigits = Math.max(0, Math
1208: .min(nDigits + exponent,
1209: nDigits));
1210: intLength = intDigits + intZeroes;
1211: decDigits = nDigits - intDigits;
1212: decZeroes = Math.max(0,
1213: -(decDigits + exponent));
1214: decLength = decZeroes + decDigits;
1215: }
1216: decDigits = precision - decZeroes;
1217: }
1218: decLength = decZeroes + decDigits;
1219: }
1220: if (precision > 0) {
1221: len += Math.max(1, intLength) + 1
1222: + precision;
1223: // (1|intlen).prec
1224: } else {
1225: len += Math.max(1, intLength);
1226: // (1|intlen)
1227: if ((flags & FLAG_SHARP) != 0) {
1228: len++; // will have a trailing '.'
1229: }
1230: }
1231:
1232: width -= len;
1233:
1234: if (width > 0
1235: && (flags & (FLAG_ZERO | FLAG_MINUS)) == 0) {
1236: buf.fill(' ', width);
1237: width = 0;
1238: }
1239: if (signChar != 0) {
1240: buf.write(signChar);
1241: }
1242: if (width > 0 && (flags & FLAG_MINUS) == 0) {
1243: buf.fill('0', width);
1244: width = 0;
1245: }
1246: // now some data...
1247: if (intLength > 0) {
1248: if (intDigits > 0) { // s/b true, since intLength > 0
1249: buf.write(digits, 0, intDigits);
1250: }
1251: if (intZeroes > 0) {
1252: buf.fill('0', intZeroes);
1253: }
1254: } else {
1255: // always need at least a 0
1256: buf.write('0');
1257: }
1258: if (precision > 0 || (flags & FLAG_SHARP) != 0) {
1259: buf.write(args.getDecimalSeparator());
1260: }
1261: if (precision > 0) {
1262: if (decZeroes > 0) {
1263: buf.fill('0', decZeroes);
1264: precision -= decZeroes;
1265: }
1266: if (decDigits > 0) {
1267: buf.write(digits, intDigits, decDigits);
1268: precision -= decDigits;
1269: }
1270: // fill up the rest with zeroes
1271: if (precision > 0) {
1272: buf.fill('0', precision);
1273: }
1274: }
1275: if (width > 0) {
1276: buf.fill(' ', width);
1277: }
1278: break;
1279: case 'E':
1280: case 'e':
1281: // intDigits isn't used here, but if it were, it would be 1
1282: /* intDigits = 1; */
1283: decDigits = nDigits - 1;
1284:
1285: if (precision < decDigits) {
1286: int n = round(digits, nDigits, precision,
1287: precision != 0);
1288: if (n > nDigits) {
1289: nDigits = n;
1290: }
1291: decDigits = Math
1292: .min(nDigits - 1, precision);
1293: }
1294: exponent += nDigits - 1;
1295: if (precision > 0) {
1296: len += 2 + precision; // n.prec
1297: } else {
1298: len += 1; // n
1299: if ((flags & FLAG_SHARP) != 0) {
1300: len++; // will have a trailing '.'
1301: }
1302: }
1303:
1304: width -= len + 5; // 5 -> e+nnn / e-nnn
1305:
1306: if (width > 0
1307: && (flags & (FLAG_ZERO | FLAG_MINUS)) == 0) {
1308: buf.fill(' ', width);
1309: width = 0;
1310: }
1311: if (signChar != 0) {
1312: buf.write(signChar);
1313: }
1314: if (width > 0 && (flags & FLAG_MINUS) == 0) {
1315: buf.fill('0', width);
1316: width = 0;
1317: }
1318: // now some data...
1319: buf.write(digits[0]);
1320: if (precision > 0) {
1321: buf.write(args.getDecimalSeparator()); // '.'
1322: if (decDigits > 0) {
1323: buf.write(digits, 1, decDigits);
1324: precision -= decDigits;
1325: }
1326: if (precision > 0) {
1327: buf.fill('0', precision);
1328: }
1329:
1330: } else if ((flags & FLAG_SHARP) != 0) {
1331: buf.write(args.getDecimalSeparator());
1332: }
1333: buf.write(expChar); // E or e
1334: buf.write(exponent >= 0 ? '+' : '-');
1335: if (exponent < 0) {
1336: exponent = -exponent;
1337: }
1338: buf.write(exponent / 100 + '0');
1339: buf.write(exponent % 100 / 10 + '0');
1340: buf.write(exponent % 10 + '0');
1341: if (width > 0) {
1342: buf.fill(' ', width);
1343: }
1344: break;
1345: } // switch (format char E,e,f,G,g)
1346:
1347: offset++;
1348: incomplete = false;
1349: break;
1350: } // block (case E,e,f,G,g)
1351: } // switch (each format char in spec)
1352: } // for (each format spec)
1353:
1354: // equivalent to MRI case '\0':
1355: if (incomplete) {
1356: if (flags == FLAG_NONE) {
1357: // dangling '%' char
1358: buf.write('%');
1359: } else {
1360: raiseArgumentError(args, ERR_ILLEGAL_FORMAT_CHAR);
1361: }
1362: }
1363: } // main while loop (offset < length)
1364:
1365: return buf.toByteList();
1366: }
1367:
1368: // debugging code, keeping for now
1369: /*
1370: private static final void showLiteral(byte[] format, int start, int offset) {
1371: System.out.println("literal: ["+ new String(format,start,offset-start)+ "], " +
1372: " s="+ start + " o="+ offset);
1373: }
1374:
1375: // debugging code, keeping for now
1376: private static final void showVals(byte[] format,int start,int offset, byte fchar,
1377: int flags, int width, int precision, Object arg) {
1378: System.out.println(new StringBuffer()
1379: .append("value: ").append(new String(format,start,offset-start+1)).append('\n')
1380: .append("type: ").append((char)fchar).append('\n')
1381: .append("start: ").append(start).append('\n')
1382: .append("length: ").append(offset-start).append('\n')
1383: .append("flags: ").append(Integer.toBinaryString(flags)).append('\n')
1384: .append("width: ").append(width).append('\n')
1385: .append("precision: ").append(precision).append('\n')
1386: .append("arg: ").append(arg).append('\n')
1387: .toString());
1388:
1389: }
1390: */
1391:
1392: private static final void raiseArgumentError(Args args,
1393: String message) {
1394: args.raiseArgumentError(message);
1395: }
1396:
1397: private static final void warning(Args args, String message) {
1398: args.warning(message);
1399: }
1400:
1401: private static final void checkOffset(Args args, int offset,
1402: int length, String message) {
1403: if (offset >= length) {
1404: raiseArgumentError(args, message);
1405: }
1406: }
1407:
1408: private static final int extendWidth(Args args, int oldWidth,
1409: byte newChar) {
1410: int newWidth = oldWidth * 10 + (newChar - '0');
1411: if (newWidth / 10 != oldWidth) {
1412: raiseArgumentError(args, "width too big");
1413: }
1414: return newWidth;
1415: }
1416:
1417: private static final boolean isDigit(byte aChar) {
1418: return (aChar >= '0' && aChar <= '9');
1419: }
1420:
1421: private static final boolean isPrintable(byte aChar) {
1422: return (aChar > 32 && aChar < 127);
1423: }
1424:
1425: private static final int skipSignBits(byte[] bytes, int base) {
1426: int skip = 0;
1427: int length = bytes.length;
1428: byte b;
1429: switch (base) {
1430: case 2:
1431: for (; skip < length && bytes[skip] == '1'; skip++)
1432: ;
1433: break;
1434: case 8:
1435: if (length > 0 && bytes[0] == '3') {
1436: skip++;
1437: }
1438: for (; skip < length && bytes[skip] == '7'; skip++)
1439: ;
1440: break;
1441: case 10:
1442: if (length > 0 && bytes[0] == '-') {
1443: skip++;
1444: }
1445: break;
1446: case 16:
1447: for (; skip < length
1448: && ((b = bytes[skip]) == 'f' || b == 'F'); skip++)
1449: ;
1450: }
1451: return skip;
1452: }
1453:
1454: private static final int round(byte[] bytes, int nDigits,
1455: int roundPos, boolean roundDown) {
1456: int next = roundPos + 1;
1457: if (next >= nDigits || bytes[next] < '5' ||
1458: // MRI rounds up on nnn5nnn, but not nnn5 --
1459: // except for when they do
1460: (roundDown && bytes[next] == '5' && next == nDigits - 1)) {
1461: return nDigits;
1462: }
1463: if (roundPos < 0) { // "%.0f" % 0.99
1464: System.arraycopy(bytes, 0, bytes, 1, nDigits);
1465: bytes[0] = '1';
1466: return nDigits + 1;
1467: }
1468: bytes[roundPos] += 1;
1469: while (bytes[roundPos] > '9') {
1470: bytes[roundPos] = '0';
1471: roundPos--;
1472: if (roundPos >= 0) {
1473: bytes[roundPos] += 1;
1474: } else {
1475: System.arraycopy(bytes, 0, bytes, 1, nDigits);
1476: bytes[0] = '1';
1477: return nDigits + 1;
1478: }
1479: }
1480: return nDigits;
1481: }
1482:
1483: private static final byte[] getFixnumBytes(RubyFixnum arg,
1484: int base, boolean sign, boolean upper) {
1485: long val = arg.getLongValue();
1486:
1487: // limit the length of negatives if possible (also faster)
1488: if (val >= Integer.MIN_VALUE && val <= Integer.MAX_VALUE) {
1489: if (sign) {
1490: return Convert.intToByteArray((int) val, base, upper);
1491: } else {
1492: switch (base) {
1493: case 2:
1494: return Convert.intToBinaryBytes((int) val);
1495: case 8:
1496: return Convert.intToOctalBytes((int) val);
1497: case 10:
1498: default:
1499: return Convert.intToCharBytes((int) val);
1500: case 16:
1501: return Convert.intToHexBytes((int) val, upper);
1502: }
1503: }
1504: } else {
1505: if (sign) {
1506: return Convert.longToByteArray(val, base, upper);
1507: } else {
1508: switch (base) {
1509: case 2:
1510: return Convert.longToBinaryBytes(val);
1511: case 8:
1512: return Convert.longToOctalBytes(val);
1513: case 10:
1514: default:
1515: return Convert.longToCharBytes(val);
1516: case 16:
1517: return Convert.longToHexBytes(val, upper);
1518: }
1519: }
1520: }
1521: }
1522:
1523: private static final byte[] getBignumBytes(RubyBignum arg,
1524: int base, boolean sign, boolean upper) {
1525: BigInteger val = arg.getValue();
1526: if (sign || base == 10 || val.signum() >= 0) {
1527: return stringToBytes(val.toString(base), upper);
1528: }
1529:
1530: // negative values
1531: byte[] bytes = val.toByteArray();
1532: switch (base) {
1533: case 2:
1534: return Convert.twosComplementToBinaryBytes(bytes);
1535: case 8:
1536: return Convert.twosComplementToOctalBytes(bytes);
1537: case 16:
1538: return Convert.twosComplementToHexBytes(bytes, upper);
1539: default:
1540: return stringToBytes(val.toString(base), upper);
1541: }
1542: }
1543:
1544: private static final byte[] getUnsignedNegativeBytes(RubyInteger arg) {
1545: // calculation for negatives when %u specified
1546: // for values >= Integer.MIN_VALUE * 2, MRI uses (the equivalent of)
1547: // long neg_u = (((long)Integer.MAX_VALUE + 1) << 1) + val
1548: // for smaller values, BigInteger math is required to conform to MRI's
1549: // result.
1550: long longval;
1551: BigInteger bigval;
1552:
1553: if (arg instanceof RubyFixnum) {
1554: // relatively cheap test for 32-bit values
1555: longval = ((RubyFixnum) arg).getLongValue();
1556: if (longval >= (long) Integer.MIN_VALUE << 1) {
1557: return Convert
1558: .longToCharBytes((((long) Integer.MAX_VALUE + 1L) << 1)
1559: + longval);
1560: }
1561: // no such luck...
1562: bigval = BigInteger.valueOf(longval);
1563: } else {
1564: bigval = ((RubyBignum) arg).getValue();
1565: }
1566: // ok, now it gets expensive...
1567: int shift = 0;
1568: // go through negated powers of 32 until we find one small enough
1569: for (BigInteger minus = BIG_MINUS_64; bigval.compareTo(minus) < 0; minus = minus
1570: .shiftLeft(32), shift++)
1571: ;
1572: // add to the corresponding positive power of 32 for the result.
1573: // meaningful? no. conformant? yes. I just write the code...
1574: BigInteger nPower32 = shift > 0 ? BIG_64.shiftLeft(32 * shift)
1575: : BIG_64;
1576: return stringToBytes(nPower32.add(bigval).toString(), false);
1577: }
1578:
1579: private static final byte[] stringToBytes(CharSequence s,
1580: boolean upper) {
1581: int len = s.length();
1582: byte[] bytes = new byte[len];
1583: if (upper) {
1584: for (int i = len; --i >= 0;) {
1585: int b = (byte) ((int) s.charAt(i) & (int) 0xff);
1586: if (b >= 'a' && b <= 'z') {
1587: bytes[i] = (byte) (b & ~0x20);
1588: } else {
1589: bytes[i] = (byte) b;
1590: }
1591: }
1592: } else {
1593: for (int i = len; --i >= 0;) {
1594: bytes[i] = (byte) ((int) s.charAt(i) & (int) 0xff);
1595: }
1596: }
1597: return bytes;
1598: }
1599: }
|