/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
* Free SoftwareFoundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.es;
import com.caucho.util.CharBuffer;
class Printf {
private final static int ALT = 0x01;
private final static int ZERO_FILL = 0x02;
private final static int POS_PLUS = 0x04;
private final static int POS_SPACE = 0x08;
private final static int LALIGN = 0x10;
private final static int BIG = 0x20;
private final static int NO_TRAIL_ZERO = 0x40;
private static char []digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f'};
private static char []bigDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F'};
private Printf()
{
}
public static String sprintf(Call eval, int length)
throws Throwable
{
if (length == 0)
return "";
CharBuffer buf = new CharBuffer();
printf(buf, eval.getArg(0).toStr(), eval, length);
return buf.toString();
}
public static CharBuffer printf(CharBuffer result, ESString format,
Call eval, int length)
throws Throwable
{
int arg = 1;
int len = format.length();
for (int i = 0; i < len; i++) {
int ch;
int start = i;
if ((ch = format.charAt(i)) != '%') {
result.append((char) ch);
continue;
}
int flags = 0;
loop:
while (++i < len) {
switch ((ch = format.charAt(i))) {
case '0': flags |= ZERO_FILL; break;
case '+': flags |= POS_PLUS; break;
case ' ': flags |= POS_SPACE; break;
case '#': flags |= ALT; break;
case '-': flags |= LALIGN; break;
default: break loop;
}
}
int width = 0;
for (; i < len && (ch = format.charAt(i)) >= '0' && ch <= '9'; i++) {
width = 10 * width + ch - '0';
}
if (i >= len) {
fixBits(result, format, start, i);
break;
}
int prec = 0;
if (ch == '.') {
while (++i < len && (ch = format.charAt(i)) >= '0' && ch <= '9') {
prec = 10 * prec + ch - '0';
}
} else
prec = -1;
if (i >= len) {
fixBits(result, format, start, i);
break;
}
switch (ch) {
case '%':
result.append((char) '%');
break;
case 'd':
if (arg >= length)
throw new ESException("missing printf argument");
formatInteger(result, eval.getArg(arg++).toNum(),
width, prec, flags, 10);
break;
case 'o':
if (arg >= length)
throw new ESException("missing printf argument");
formatInteger(result, eval.getArg(arg++).toNum(),
width, prec, flags, 8);
break;
case 'X':
flags |= BIG;
case 'x':
if (arg >= length)
throw new ESException("missing printf argument");
formatInteger(result, eval.getArg(arg++).toNum(),
width, prec, flags, 16);
break;
case 'E':
case 'G':
flags |= BIG;
case 'f':
case 'e':
case 'g':
if (arg >= length)
throw new ESException("missing printf argument");
formatDouble(result, eval.getArg(arg++).toNum(),
width, prec, flags, ch);
break;
case 'c':
if (arg >= length)
throw new ESException("missing printf argument");
formatChar(result, (int) eval.getArg(arg++).toNum(), width, flags);
break;
case 's':
if (arg >= length)
throw new ESException("missing printf argument");
formatString(result, eval.getArg(arg++).toStr(),
prec, width, flags);
break;
default:
fixBits(result, format, start, i + 1);
break;
}
}
return result;
}
private static void formatDouble(CharBuffer cb, double value,
int prec, int flags, int type)
{
String raw = Double.toString(value);
int expt = 0;
int i = 0;
CharBuffer digits = new CharBuffer();
int ch = raw.charAt(i);
boolean seenDigit = false;
// XXX: locale screws us?
for (; i < raw.length(); i++) {
if ((ch = raw.charAt(i)) == '.' || ch == 'e' || ch == 'E')
break;
else if (! seenDigit && ch == '0') {
}
else {
seenDigit = true;
digits.append((char) ch);
expt++;
}
}
if (ch == '.')
i++;
for (; i < raw.length(); i++) {
ch = raw.charAt(i);
if (! seenDigit && ch == '0') {
expt--;
} else if (ch >= '0' && ch <= '9') {
digits.append((char) ch);
seenDigit = true;
}
else {
int sign = 1;
i++;
if ((ch = raw.charAt(i)) == '+') {
i++;
}
else if (ch == '-') {
i++;
sign = -1;
}
int e = 0;
for (; i < raw.length() && (ch = raw.charAt(i)) >= '0' && ch <= '9';
i++) {
e = 10 * e + ch - '0';
}
expt += sign * e;
break;
}
}
if (! seenDigit)
expt = 1;
while (digits.length() > 0 && digits.charAt(digits.length() - 1) == '0')
digits.setLength(digits.length() - 1);
if (type == 'f') {
if (roundDigits(digits, expt + prec)) {
expt++;
}
formatFixed(cb, digits, expt, prec, flags);
}
else if (type == 'e' || type == 'E') {
if (roundDigits(digits, prec + 1))
expt++;
formatExpt(cb, digits, expt, prec, flags);
}
else {
if (roundDigits(digits, prec))
expt++;
if (expt < -3 || expt > prec)
formatExpt(cb, digits, expt, prec - 1, flags|NO_TRAIL_ZERO);
else
formatFixed(cb, digits, expt, prec - expt, flags|NO_TRAIL_ZERO);
}
}
private static void formatDouble(CharBuffer cb, double value,
int width, int prec, int flags,
int type)
{
if (prec < 0)
prec = 6;
int offset = cb.length();
if ((flags & ZERO_FILL) != 0 &&
(value < 0 || (flags & (POS_PLUS|POS_SPACE)) != 0)) {
offset++;
width--;
}
if (value < 0) {
cb.append((char) '-');
value = -value;
} else if ((flags & POS_PLUS) != 0) {
cb.append((char) '+');
} else if ((flags & POS_SPACE) != 0) {
cb.append((char) ' ');
}
formatDouble(cb, value, prec, flags, type);
width -= cb.length() - offset;
for (int i = 0; i < width; i++) {
if ((flags & LALIGN) != 0)
cb.append(' ');
else
cb.insert(offset, (flags & ZERO_FILL) == 0 ? ' ' : '0');
}
}
private static boolean roundDigits(CharBuffer digits, int len)
{
if (len < 0 || digits.length() <= len)
return false;
int value = digits.charAt(len);
if (value < '5')
return false;
for (int i = len - 1; i >= 0; i--) {
int ch = digits.charAt(i);
if (ch != '9') {
digits.setCharAt(i, (char) (ch + 1));
return false;
}
digits.setCharAt(i, '0');
}
digits.insert(0, '1');
return true;
}
private static void formatFixed(CharBuffer cb, CharBuffer digits,
int expt, int prec, int flags)
{
int i = 0;
int origExpt = expt;
for (; expt > 0; expt--) {
if (i < digits.length())
cb.append((char) digits.charAt(i++));
else
cb.append('0');
}
if (origExpt <= 0) // || digits.length() == 0)
cb.append('0');
if (prec > 0 || (flags & ALT) != 0)
cb.append('.');
for (; expt < 0 && prec > 0; expt++) {
cb.append('0');
prec--;
}
for (; prec > 0 && i < digits.length(); i++) {
cb.append(digits.charAt(i));
prec--;
}
for (; prec > 0 && (flags & (NO_TRAIL_ZERO|ALT)) != NO_TRAIL_ZERO; prec--)
cb.append('0');
}
private static void formatExpt(CharBuffer cb, CharBuffer digits,
int expt, int prec, int flags)
{
if (digits.length() == 0)
cb.append('0');
else
cb.append((char) digits.charAt(0));
if (prec > 0 || (flags & ALT) != 0)
cb.append('.');
for (int i = 1; i < digits.length(); i++) {
if (prec > 0)
cb.append((char) digits.charAt(i));
prec--;
}
for (; prec > 0 && (flags & (NO_TRAIL_ZERO|ALT)) != NO_TRAIL_ZERO; prec--)
cb.append('0');
if ((flags & BIG) != 0)
cb.append('E');
else
cb.append('e');
formatInteger(cb, expt - 1, 0, 2, POS_PLUS, 10);
}
private static void formatInteger(CharBuffer cb, double dvalue,
int width, int prec, int flags, int radix)
{
boolean isBig = (flags & BIG) != 0;
int begin = cb.length();
long value;
if (dvalue > 0)
value = (long) (dvalue + 0.5);
else
value = (long) (dvalue - 0.5);
if (value < 0 && radix == 10) {
cb.append((char) '-');
value = -value;
} else if (value >= 0 && radix == 10 && (flags & POS_PLUS) != 0)
cb.append((char) '+');
else if (value >= 0 && radix == 10 && (flags & POS_SPACE) != 0)
cb.append((char) ' ');
else if (value < 0)
value &= 0xffffffffL;
else if (radix == 8 && (flags & ALT) != 0 && value != 0)
cb.append('0');
else if (radix == 16 && (flags & ALT) != 0)
cb.append((flags & BIG) == 0 ? "0x" : "0X");
if ((flags & ZERO_FILL) != 0) {
width -= cb.length() - begin;
begin = cb.length();
}
int offset = cb.length();
int len = 0;
while (value != 0) {
len++;
cb.insert(offset, (isBig ? bigDigits : digits)[(int) (value % radix)]);
value /= radix;
}
for (int i = 0; i < prec - len; i++)
cb.insert(offset, '0');
if (len == 0 && prec == 0)
cb.insert(offset, '0');
width -= cb.length() - begin;
for (; width > 0; width--) {
if ((flags & LALIGN) != 0)
cb.append(' ');
else if ((flags & ZERO_FILL) != 0 && prec < 0)
cb.insert(begin, '0');
else
cb.insert(begin, ' ');
}
if (cb.length() == begin)
cb.append('0');
}
private static void formatChar(CharBuffer cb, int ch, int width, int flags)
{
int offset = cb.length();
cb.append((char) ch);
if ((flags & LALIGN) == 0) {
for (int i = 0; i < width - 1; i++)
cb.insert(offset, (char) ' ');
} else {
for (int i = 0; i < width - 1; i++)
cb.append((char) ' ');
}
}
private static void formatString(CharBuffer cb, ESString string,
int prec, int width, int flags)
{
int offset = cb.length();
if (prec < 0)
prec = Integer.MAX_VALUE;
for (int i = 0; i < string.length() && i < prec; i++) {
width--;
cb.append(string.charAt(i));
}
if ((flags & LALIGN) == 0) {
for (int i = 0; i < width; i++)
cb.insert(offset, (char) ' ');
} else {
for (int i = 0; i < width; i++)
cb.append((char) ' ');
}
}
private static void fixBits(CharBuffer cb, ESString format, int s, int i)
{
for (; s < i; s++)
cb.append((char) format.charAt(s));
}
}