001: /*
002: * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: * Free SoftwareFoundation, Inc.
023: * 59 Temple Place, Suite 330
024: * Boston, MA 02111-1307 USA
025: *
026: * @author Scott Ferguson
027: */
028:
029: package com.caucho.es;
030:
031: import com.caucho.util.CharBuffer;
032:
033: class Printf {
034: private final static int ALT = 0x01;
035: private final static int ZERO_FILL = 0x02;
036: private final static int POS_PLUS = 0x04;
037: private final static int POS_SPACE = 0x08;
038: private final static int LALIGN = 0x10;
039: private final static int BIG = 0x20;
040: private final static int NO_TRAIL_ZERO = 0x40;
041:
042: private static char[] digits = { '0', '1', '2', '3', '4', '5', '6',
043: '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
044: private static char[] bigDigits = { '0', '1', '2', '3', '4', '5',
045: '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
046:
047: private Printf() {
048: }
049:
050: public static String sprintf(Call eval, int length)
051: throws Throwable {
052: if (length == 0)
053: return "";
054:
055: CharBuffer buf = new CharBuffer();
056:
057: printf(buf, eval.getArg(0).toStr(), eval, length);
058:
059: return buf.toString();
060: }
061:
062: public static CharBuffer printf(CharBuffer result, ESString format,
063: Call eval, int length) throws Throwable {
064: int arg = 1;
065: int len = format.length();
066:
067: for (int i = 0; i < len; i++) {
068: int ch;
069: int start = i;
070:
071: if ((ch = format.charAt(i)) != '%') {
072: result.append((char) ch);
073: continue;
074: }
075:
076: int flags = 0;
077: loop: while (++i < len) {
078: switch ((ch = format.charAt(i))) {
079: case '0':
080: flags |= ZERO_FILL;
081: break;
082: case '+':
083: flags |= POS_PLUS;
084: break;
085: case ' ':
086: flags |= POS_SPACE;
087: break;
088: case '#':
089: flags |= ALT;
090: break;
091: case '-':
092: flags |= LALIGN;
093: break;
094:
095: default:
096: break loop;
097: }
098: }
099:
100: int width = 0;
101: for (; i < len && (ch = format.charAt(i)) >= '0'
102: && ch <= '9'; i++) {
103: width = 10 * width + ch - '0';
104: }
105:
106: if (i >= len) {
107: fixBits(result, format, start, i);
108: break;
109: }
110:
111: int prec = 0;
112: if (ch == '.') {
113: while (++i < len && (ch = format.charAt(i)) >= '0'
114: && ch <= '9') {
115: prec = 10 * prec + ch - '0';
116: }
117: } else
118: prec = -1;
119:
120: if (i >= len) {
121: fixBits(result, format, start, i);
122: break;
123: }
124:
125: switch (ch) {
126: case '%':
127: result.append((char) '%');
128: break;
129:
130: case 'd':
131: if (arg >= length)
132: throw new ESException("missing printf argument");
133: formatInteger(result, eval.getArg(arg++).toNum(),
134: width, prec, flags, 10);
135: break;
136:
137: case 'o':
138: if (arg >= length)
139: throw new ESException("missing printf argument");
140: formatInteger(result, eval.getArg(arg++).toNum(),
141: width, prec, flags, 8);
142: break;
143:
144: case 'X':
145: flags |= BIG;
146: case 'x':
147: if (arg >= length)
148: throw new ESException("missing printf argument");
149: formatInteger(result, eval.getArg(arg++).toNum(),
150: width, prec, flags, 16);
151: break;
152:
153: case 'E':
154: case 'G':
155: flags |= BIG;
156: case 'f':
157: case 'e':
158: case 'g':
159: if (arg >= length)
160: throw new ESException("missing printf argument");
161: formatDouble(result, eval.getArg(arg++).toNum(), width,
162: prec, flags, ch);
163: break;
164:
165: case 'c':
166: if (arg >= length)
167: throw new ESException("missing printf argument");
168: formatChar(result, (int) eval.getArg(arg++).toNum(),
169: width, flags);
170: break;
171:
172: case 's':
173: if (arg >= length)
174: throw new ESException("missing printf argument");
175: formatString(result, eval.getArg(arg++).toStr(), prec,
176: width, flags);
177: break;
178:
179: default:
180: fixBits(result, format, start, i + 1);
181: break;
182: }
183: }
184:
185: return result;
186: }
187:
188: private static void formatDouble(CharBuffer cb, double value,
189: int prec, int flags, int type) {
190: String raw = Double.toString(value);
191: int expt = 0;
192: int i = 0;
193: CharBuffer digits = new CharBuffer();
194: int ch = raw.charAt(i);
195: boolean seenDigit = false;
196:
197: // XXX: locale screws us?
198: for (; i < raw.length(); i++) {
199: if ((ch = raw.charAt(i)) == '.' || ch == 'e' || ch == 'E')
200: break;
201: else if (!seenDigit && ch == '0') {
202: } else {
203: seenDigit = true;
204: digits.append((char) ch);
205: expt++;
206: }
207: }
208:
209: if (ch == '.')
210: i++;
211:
212: for (; i < raw.length(); i++) {
213: ch = raw.charAt(i);
214:
215: if (!seenDigit && ch == '0') {
216: expt--;
217: } else if (ch >= '0' && ch <= '9') {
218: digits.append((char) ch);
219: seenDigit = true;
220: } else {
221: int sign = 1;
222: i++;
223: if ((ch = raw.charAt(i)) == '+') {
224: i++;
225: } else if (ch == '-') {
226: i++;
227: sign = -1;
228: }
229:
230: int e = 0;
231: for (; i < raw.length() && (ch = raw.charAt(i)) >= '0'
232: && ch <= '9'; i++) {
233: e = 10 * e + ch - '0';
234: }
235:
236: expt += sign * e;
237: break;
238: }
239: }
240:
241: if (!seenDigit)
242: expt = 1;
243:
244: while (digits.length() > 0
245: && digits.charAt(digits.length() - 1) == '0')
246: digits.setLength(digits.length() - 1);
247:
248: if (type == 'f') {
249: if (roundDigits(digits, expt + prec)) {
250: expt++;
251: }
252:
253: formatFixed(cb, digits, expt, prec, flags);
254: } else if (type == 'e' || type == 'E') {
255: if (roundDigits(digits, prec + 1))
256: expt++;
257:
258: formatExpt(cb, digits, expt, prec, flags);
259: } else {
260: if (roundDigits(digits, prec))
261: expt++;
262:
263: if (expt < -3 || expt > prec)
264: formatExpt(cb, digits, expt, prec - 1, flags
265: | NO_TRAIL_ZERO);
266: else
267: formatFixed(cb, digits, expt, prec - expt, flags
268: | NO_TRAIL_ZERO);
269: }
270: }
271:
272: private static void formatDouble(CharBuffer cb, double value,
273: int width, int prec, int flags, int type) {
274: if (prec < 0)
275: prec = 6;
276:
277: int offset = cb.length();
278:
279: if ((flags & ZERO_FILL) != 0
280: && (value < 0 || (flags & (POS_PLUS | POS_SPACE)) != 0)) {
281: offset++;
282: width--;
283: }
284:
285: if (value < 0) {
286: cb.append((char) '-');
287: value = -value;
288: } else if ((flags & POS_PLUS) != 0) {
289: cb.append((char) '+');
290: } else if ((flags & POS_SPACE) != 0) {
291: cb.append((char) ' ');
292: }
293:
294: formatDouble(cb, value, prec, flags, type);
295:
296: width -= cb.length() - offset;
297:
298: for (int i = 0; i < width; i++) {
299: if ((flags & LALIGN) != 0)
300: cb.append(' ');
301: else
302: cb.insert(offset, (flags & ZERO_FILL) == 0 ? ' ' : '0');
303: }
304: }
305:
306: private static boolean roundDigits(CharBuffer digits, int len) {
307: if (len < 0 || digits.length() <= len)
308: return false;
309:
310: int value = digits.charAt(len);
311: if (value < '5')
312: return false;
313:
314: for (int i = len - 1; i >= 0; i--) {
315: int ch = digits.charAt(i);
316:
317: if (ch != '9') {
318: digits.setCharAt(i, (char) (ch + 1));
319: return false;
320: }
321: digits.setCharAt(i, '0');
322: }
323:
324: digits.insert(0, '1');
325:
326: return true;
327: }
328:
329: private static void formatFixed(CharBuffer cb, CharBuffer digits,
330: int expt, int prec, int flags) {
331: int i = 0;
332: int origExpt = expt;
333:
334: for (; expt > 0; expt--) {
335: if (i < digits.length())
336: cb.append((char) digits.charAt(i++));
337: else
338: cb.append('0');
339: }
340:
341: if (origExpt <= 0) // || digits.length() == 0)
342: cb.append('0');
343:
344: if (prec > 0 || (flags & ALT) != 0)
345: cb.append('.');
346:
347: for (; expt < 0 && prec > 0; expt++) {
348: cb.append('0');
349: prec--;
350: }
351:
352: for (; prec > 0 && i < digits.length(); i++) {
353: cb.append(digits.charAt(i));
354: prec--;
355: }
356:
357: for (; prec > 0
358: && (flags & (NO_TRAIL_ZERO | ALT)) != NO_TRAIL_ZERO; prec--)
359: cb.append('0');
360: }
361:
362: private static void formatExpt(CharBuffer cb, CharBuffer digits,
363: int expt, int prec, int flags) {
364: if (digits.length() == 0)
365: cb.append('0');
366: else
367: cb.append((char) digits.charAt(0));
368:
369: if (prec > 0 || (flags & ALT) != 0)
370: cb.append('.');
371:
372: for (int i = 1; i < digits.length(); i++) {
373: if (prec > 0)
374: cb.append((char) digits.charAt(i));
375: prec--;
376: }
377:
378: for (; prec > 0
379: && (flags & (NO_TRAIL_ZERO | ALT)) != NO_TRAIL_ZERO; prec--)
380: cb.append('0');
381:
382: if ((flags & BIG) != 0)
383: cb.append('E');
384: else
385: cb.append('e');
386:
387: formatInteger(cb, expt - 1, 0, 2, POS_PLUS, 10);
388: }
389:
390: private static void formatInteger(CharBuffer cb, double dvalue,
391: int width, int prec, int flags, int radix) {
392: boolean isBig = (flags & BIG) != 0;
393: int begin = cb.length();
394:
395: long value;
396: if (dvalue > 0)
397: value = (long) (dvalue + 0.5);
398: else
399: value = (long) (dvalue - 0.5);
400:
401: if (value < 0 && radix == 10) {
402: cb.append((char) '-');
403: value = -value;
404: } else if (value >= 0 && radix == 10 && (flags & POS_PLUS) != 0)
405: cb.append((char) '+');
406: else if (value >= 0 && radix == 10 && (flags & POS_SPACE) != 0)
407: cb.append((char) ' ');
408: else if (value < 0)
409: value &= 0xffffffffL;
410: else if (radix == 8 && (flags & ALT) != 0 && value != 0)
411: cb.append('0');
412: else if (radix == 16 && (flags & ALT) != 0)
413: cb.append((flags & BIG) == 0 ? "0x" : "0X");
414:
415: if ((flags & ZERO_FILL) != 0) {
416: width -= cb.length() - begin;
417: begin = cb.length();
418: }
419:
420: int offset = cb.length();
421: int len = 0;
422: while (value != 0) {
423: len++;
424: cb
425: .insert(
426: offset,
427: (isBig ? bigDigits : digits)[(int) (value % radix)]);
428: value /= radix;
429: }
430:
431: for (int i = 0; i < prec - len; i++)
432: cb.insert(offset, '0');
433: if (len == 0 && prec == 0)
434: cb.insert(offset, '0');
435:
436: width -= cb.length() - begin;
437: for (; width > 0; width--) {
438: if ((flags & LALIGN) != 0)
439: cb.append(' ');
440: else if ((flags & ZERO_FILL) != 0 && prec < 0)
441: cb.insert(begin, '0');
442: else
443: cb.insert(begin, ' ');
444: }
445:
446: if (cb.length() == begin)
447: cb.append('0');
448: }
449:
450: private static void formatChar(CharBuffer cb, int ch, int width,
451: int flags) {
452: int offset = cb.length();
453:
454: cb.append((char) ch);
455:
456: if ((flags & LALIGN) == 0) {
457: for (int i = 0; i < width - 1; i++)
458: cb.insert(offset, (char) ' ');
459: } else {
460: for (int i = 0; i < width - 1; i++)
461: cb.append((char) ' ');
462: }
463: }
464:
465: private static void formatString(CharBuffer cb, ESString string,
466: int prec, int width, int flags) {
467: int offset = cb.length();
468:
469: if (prec < 0)
470: prec = Integer.MAX_VALUE;
471:
472: for (int i = 0; i < string.length() && i < prec; i++) {
473: width--;
474: cb.append(string.charAt(i));
475: }
476:
477: if ((flags & LALIGN) == 0) {
478: for (int i = 0; i < width; i++)
479: cb.insert(offset, (char) ' ');
480: } else {
481: for (int i = 0; i < width; i++)
482: cb.append((char) ' ');
483: }
484: }
485:
486: private static void fixBits(CharBuffer cb, ESString format, int s,
487: int i) {
488: for (; s < i; s++)
489: cb.append((char) format.charAt(s));
490: }
491: }
|