001: // AMB - obtained 5/9/03 from http://www.cs.helsinki.fi/u/abrax/HACK/JAVA/PRINTF.html
002: /*
003: * FormatStringBuffer: printf style output formatter for Java
004: * Copyright (C) 2002 Antti S. Brax
005: * All rights reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * - Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * - Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in the
016: * documentation and/or other materials provided with the distribution.
017: *
018: * - The names of the contributors may not be used to endorse or promote
019: * products derived from this software without specific prior written
020: * permission.
021: *
022: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
023: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
024: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
025: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
026: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
027: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
028: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
029: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
030: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
031: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
032: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
033: */
034: package uk.org.ponder.stringutil;
035:
036: import java.text.NumberFormat;
037: import java.text.DecimalFormat;
038:
039: /**
040: * A class for formatting output similar to the C <tt>printf</tt> command.
041: *
042: * <P>Some features provided by ANSI C-standard conformant <tt>printfs</tt>
043: * are not supported because of language constraints.
044: *
045: * <P>Supported conversion specifiers are: 'c', 'd', 'e', 'E', 'f', 'g'
046: * (works like 'f'), 'i', 'o', 's', 'x' and 'X'.
047: *
048: * <P>Supported conversion flags are: '#', '0', '-', ' ' (a space) and '+'.
049: *
050: * <P>Support for conversion flag '*' is under development.
051: *
052: * @author Antti S. Brax (asb@iki.fi, base implementation)
053: * @author Fred Long (flong(AT)skcc.org, implemented 'e', 'E' and 'g')
054: * @version 1.7
055: */
056: public class FormatStringBuffer {
057:
058: // ==================================================================== //
059:
060: /** Pad with zero instead of space. */
061: private static final int ZEROPAD = 1;
062:
063: /** Unsigned/signed long. */
064: private static final int SIGN = 2;
065:
066: /** Show plus sign. */
067: private static final int PLUS = 4;
068:
069: /** Space if plus. */
070: private static final int SPACE = 8;
071:
072: /** Left justified. */
073: private static final int LEFT = 16;
074:
075: /** Prepend hex digits with '0x' and octal with '0' */
076: private static final int SPECIAL = 32;
077:
078: /** Use upper case hex digits. */
079: private static final int LARGE = 64;
080:
081: /** Use scientific notation */
082: private static final int SCI = 128;
083:
084: /** Use uppercase E */
085: private static final int UPPER = 256;
086:
087: /** Use grouping character */
088: private static final int GROUPING = 512;
089:
090: // ==================================================================== //
091:
092: /** Format a char. */
093: private static final int CHAR = 0;
094:
095: /** Format a String. */
096: private static final int STRING = 1;
097:
098: /** Format a decimal number. */
099: private static final int DECIMAL = 2;
100:
101: /** Format a floating point number. */
102: private static final int FLOAT = 3;
103:
104: // ==================================================================== //
105:
106: /** The format string. */
107: private String format = null;
108:
109: /** The buffer. */
110: private StringBuffer buffer = null;
111:
112: /** The current index. */
113: private int index = 0;
114:
115: // ==================================================================== //
116:
117: /**
118: * Create a new <tt>FormatStringBuffer</tt>.
119: *
120: * @param format the format string.
121: */
122: public FormatStringBuffer(String format) {
123: reset(format);
124: }
125:
126: /**
127: * Reset this <tt>FormatStringBuffer</tt>.
128: *
129: * @param format the format string.
130: */
131: public FormatStringBuffer reset(String format) {
132: reset();
133: this .format = format;
134: return this ;
135: }
136:
137: /**
138: * Reset this <tt>FormatStringBuffer</tt> with the format string
139: * given in the constructor or last call to <tt>reset(String)</tt>.
140: * This is automatically called after <tt>toString()</tt>.
141: *
142: * @param format the format string.
143: */
144: public FormatStringBuffer reset() {
145: this .buffer = new StringBuffer();
146: this .index = 0;
147: return this ;
148: }
149:
150: // ==================================================================== //
151:
152: /**
153: * Get the next format token from the format string. Copy every
154: * character from <tt>format</tt> to <tt>buffer</tt> between
155: * <tt>index</tt> and the next format token.
156: */
157: private Format getFormat() {
158:
159: char ch;
160:
161: while (index < format.length()) {
162: if ((ch = format.charAt(index)) != '%') {
163: buffer.append(ch);
164: index++;
165: continue;
166: }
167:
168: Format fmt = new Format();
169:
170: // Process flags.
171: boolean repeat = true;
172: while (repeat) {
173:
174: if (index + 1 >= format.length())
175: throw new IllegalArgumentException(
176: "Malformed format");
177:
178: switch (ch = format.charAt(++index)) { // Skip the first '%'
179: case '-':
180: fmt.flags |= LEFT;
181: break;
182: case '+':
183: fmt.flags |= PLUS;
184: break;
185: case ' ':
186: fmt.flags |= SPACE;
187: break;
188: case '#':
189: fmt.flags |= SPECIAL;
190: break;
191: case '0':
192: fmt.flags |= ZEROPAD;
193: break;
194: case '\'':
195: fmt.flags |= GROUPING;
196: break;
197: default:
198: repeat = false;
199: break;
200: }
201: }
202:
203: // Get field width.
204: if (Character.isDigit(ch)) {
205: // Explicit number.
206: fmt.fieldWidth = skipDigits();
207: }
208:
209: if (index >= format.length())
210: throw new IllegalArgumentException("Malformed format");
211:
212: // Get precision.
213: if ((ch = format.charAt(index)) == '.') {
214:
215: if (++index >= format.length())
216: throw new IllegalArgumentException(
217: "Malformed format");
218:
219: fmt.precision = skipDigits();
220: if (fmt.precision < 0) {
221: fmt.precision = 0;
222: }
223: }
224:
225: if (index >= format.length())
226: throw new IllegalArgumentException("Malformed format");
227:
228: switch (ch = format.charAt(index++)) {
229: case 'c':
230: fmt.type = CHAR;
231: return fmt;
232: case 's':
233: fmt.type = STRING;
234: return fmt;
235: case '%':
236: buffer.append('%');
237: continue;
238:
239: // Octal, hexadecimal and decimal.
240:
241: case 'o':
242: fmt.type = DECIMAL;
243: fmt.base = 8;
244: return fmt;
245: case 'X':
246: fmt.flags |= LARGE;
247: case 'x':
248: fmt.type = DECIMAL;
249: fmt.base = 16;
250: return fmt;
251: case 'd':
252: case 'i':
253: fmt.type = DECIMAL;
254: return fmt;
255:
256: // Floating point
257:
258: case 'f':
259: case 'g':
260: fmt.type = FLOAT;
261: return fmt;
262: case 'e':
263: fmt.type = FLOAT;
264: fmt.flags |= SCI;
265: return fmt;
266: case 'E':
267: fmt.type = FLOAT;
268: fmt.flags |= SCI;
269: fmt.flags |= UPPER;
270: return fmt;
271: default:
272: buffer.append('%');
273: buffer.append(ch);
274: continue;
275: }
276: }
277:
278: return null;
279: }
280:
281: /**
282: * Skip digits and return the number they form.
283: */
284: private int skipDigits() {
285: char ch;
286: int i = 0;
287:
288: while (index < format.length()) {
289: if (Character.isDigit(ch = format.charAt(index))) {
290: index++;
291: i = i * 10 + Character.digit(ch, 10);
292: } else {
293: break;
294: }
295: }
296: return i;
297: }
298:
299: // ==================================================================== //
300:
301: /**
302: * Format a <tt>char</tt>.
303: */
304: public FormatStringBuffer format(char ch) {
305:
306: Format fmt = getFormat();
307:
308: if (fmt.type != CHAR)
309: throw new IllegalArgumentException("Expected a char format");
310:
311: if ((fmt.flags & LEFT) != LEFT)
312: while (--fmt.fieldWidth > 0)
313: buffer.append(' ');
314: buffer.append(ch);
315: while (--fmt.fieldWidth > 0)
316: buffer.append(' ');
317:
318: return this ;
319: }
320:
321: /**
322: * Format a <tt>float</tt>.
323: */
324: public FormatStringBuffer format(float flt) {
325:
326: return format((double) flt);
327:
328: }
329:
330: /**
331: * Format a <tt>double</tt>.
332: */
333: public FormatStringBuffer format(double dbl) {
334:
335: Format fmt = getFormat();
336:
337: if (fmt.type != FLOAT)
338: throw new IllegalArgumentException(
339: "Expected a float format");
340:
341: NumberFormat nf;
342: if ((fmt.flags & SCI) > 0)
343: nf = new DecimalFormat("0.#E00");
344: else
345: nf = NumberFormat.getInstance();
346: nf.setGroupingUsed((fmt.flags & GROUPING) != 0);
347: if (fmt.precision != -1) {
348: nf.setMaximumFractionDigits(fmt.precision);
349: nf.setMinimumFractionDigits(fmt.precision);
350: } else {
351: nf.setMaximumFractionDigits(Integer.MAX_VALUE);
352: nf.setMinimumFractionDigits(1);
353: }
354: String str = nf.format(dbl);
355: if ((fmt.flags & SCI) == SCI && (fmt.flags & UPPER) == 0) {
356: str = str.replace('E', 'e');
357: }
358: if ((fmt.flags & PLUS) == PLUS && dbl >= 0.0)
359: str = "+" + str;
360:
361: int len = str.length();
362: if ((fmt.flags & LEFT) != LEFT)
363: while (len < fmt.fieldWidth--)
364: buffer.append(' ');
365:
366: for (int i = 0; i < len; ++i)
367: buffer.append(str.charAt(i));
368:
369: while (len < fmt.fieldWidth--)
370: buffer.append(' ');
371:
372: return this ;
373: }
374:
375: /**
376: * Format a <tt>float</tt>.
377: */
378: public FormatStringBuffer format(int i) {
379:
380: return format((long) i);
381:
382: }
383:
384: /**
385: * Format a <tt>float</tt>.
386: */
387: public FormatStringBuffer format(long l) {
388:
389: Format fmt = getFormat();
390:
391: if (fmt.type != DECIMAL)
392: throw new IllegalArgumentException(
393: "Expected a float format");
394:
395: // Decide padding character.
396: char pad = ' ';
397: if ((fmt.flags & ZEROPAD) == ZEROPAD) {
398: pad = '0';
399: }
400:
401: // Convert numberto String.
402: String str;
403: String prefix = "";
404: switch (fmt.base) {
405: case 8:
406: str = Long.toOctalString(l);
407: if ((fmt.flags & SPECIAL) == SPECIAL) {
408: fmt.fieldWidth -= 1;
409: prefix = "0";
410: }
411: break;
412: case 16:
413: str = Long.toHexString(l);
414: if ((fmt.flags & SPECIAL) == SPECIAL) {
415: fmt.fieldWidth -= 2;
416: prefix = "0x";
417: }
418: break;
419: default:
420: str = String.valueOf(Math.abs(l));
421: break;
422: }
423:
424: if ((fmt.flags & LARGE) == LARGE) {
425: str = str.toUpperCase();
426: prefix = prefix.toUpperCase();
427: }
428:
429: int len = str.length();
430:
431: if (l < 0 || (fmt.flags & PLUS) == PLUS) {
432: fmt.fieldWidth--;
433: }
434:
435: // Place the sign character first if zero padding.
436: if ((fmt.flags & ZEROPAD) == ZEROPAD) {
437: if (l < 0 && fmt.base == 10) {
438: buffer.append('-');
439: } else if ((fmt.flags & PLUS) == PLUS && fmt.base == 10) {
440: buffer.append('+');
441: }
442: buffer.append(prefix);
443: }
444:
445: // Pad.
446: if ((fmt.flags & LEFT) != LEFT)
447: while (len < fmt.fieldWidth--)
448: buffer.append(pad);
449:
450: // Place the sign character now if not zero padding.
451: if ((fmt.flags & ZEROPAD) != ZEROPAD) {
452: if (l < 0 && fmt.base == 10) {
453: buffer.append('-');
454: } else if ((fmt.flags & PLUS) == PLUS && fmt.base == 10) {
455: buffer.append('+');
456: }
457: buffer.append(prefix);
458: }
459:
460: for (int i = 0; i < len; ++i)
461: buffer.append(str.charAt(i));
462:
463: while (len < fmt.fieldWidth--)
464: buffer.append(' ');
465:
466: return this ;
467: }
468:
469: /**
470: * Format a <tt>String</tt>.
471: */
472: public FormatStringBuffer format(String str) {
473:
474: if (str == null)
475: str = "<NULL>";
476:
477: Format fmt = getFormat();
478:
479: if (fmt.type != STRING)
480: throw new IllegalArgumentException(
481: "Expected a String format");
482:
483: int len = str.length();
484: if (fmt.precision != -1 && len > fmt.precision)
485: len = fmt.precision;
486:
487: if ((fmt.flags & LEFT) != LEFT)
488: while (len < fmt.fieldWidth--)
489: buffer.append(' ');
490:
491: for (int i = 0; i < len; ++i)
492: buffer.append(str.charAt(i));
493:
494: while (len < fmt.fieldWidth--)
495: buffer.append(' ');
496:
497: return this ;
498: }
499:
500: // ==================================================================== //
501:
502: /**
503: * Get the result of the formatting. <tt>reset()</tt> is automatically
504: * called from this method.
505: */
506: public String toString() {
507:
508: if (index < format.length())
509: buffer.append(format.substring(index));
510:
511: String str = buffer.toString();
512: this .reset();
513:
514: return str;
515: }
516:
517: // ==================================================================== //
518:
519: /**
520: * A container class for several format parameters.
521: */
522: private class Format {
523: public int flags = 0;
524: public int fieldWidth = -1;
525: public int precision = -1;
526: public int type = -1;
527: public int base = 10;
528: }
529:
530: // ==================================================================== //
531:
532: // /*
533:
534: public static void test(String str1, String str2) {
535:
536: System.err.print("Expected " + str1 + " got " + str2);
537: if (!str1.equals(str2))
538: System.err.println(" <--- ERROR!");
539: else
540: System.err.println();
541: }
542:
543: public static void main(String args[]) {
544:
545: FormatStringBuffer fsb = new FormatStringBuffer("[%s]");
546:
547: // STRING
548:
549: fsb.reset("[%s]").format("test");
550: test("[test]", fsb.toString());
551:
552: fsb.reset("[%5s]").format("test");
553: test("[ test]", fsb.toString());
554:
555: fsb.reset("[%-5s]").format("test");
556: test("[test ]", fsb.toString());
557:
558: fsb.reset("[%5.2s]").format("test");
559: test("[ te]", fsb.toString());
560:
561: fsb.reset("[%-5.2s]").format("test");
562: test("[te ]", fsb.toString());
563:
564: // CHAR
565:
566: fsb.reset("[%c]").format('A');
567: test("[A]", fsb.toString());
568:
569: fsb.reset("[%2c]").format('A');
570: test("[ A]", fsb.toString());
571:
572: fsb.reset("[%-2c]").format('A');
573: test("[A ]", fsb.toString());
574:
575: // FLOAT
576:
577: fsb.reset("[%f]").format(3.1415);
578: test("[3.1415]", fsb.toString());
579:
580: fsb.reset("[%g]").format(3.1415);
581: test("[3.1415]", fsb.toString());
582:
583: fsb.reset("[%+f]").format(3.1415);
584: test("[+3.1415]", fsb.toString());
585:
586: fsb.reset("[%+10f]").format(3.1415);
587: test("[ +3.1415]", fsb.toString());
588:
589: fsb.reset("[%-+10f]").format(3.1415);
590: test("[+3.1415 ]", fsb.toString());
591:
592: fsb.reset("[%.3f]").format(3.1415);
593: test("[3.142]", fsb.toString());
594:
595: fsb.reset("[%e]").format(3.1415);
596: test("[3.1415e00]", fsb.toString());
597:
598: fsb.reset("[%+e]").format(3.1415);
599: test("[+3.1415e00]", fsb.toString());
600:
601: fsb.reset("[%+11e]").format(3.1415);
602: test("[ +3.1415e00]", fsb.toString());
603:
604: fsb.reset("[%-+11e]").format(3.1415);
605: test("[+3.1415e00 ]", fsb.toString());
606:
607: fsb.reset("[%.3e]").format(3.1415);
608: test("[3.142e00]", fsb.toString());
609:
610: fsb.reset("[%E]").format(3.1415);
611: test("[3.1415E00]", fsb.toString());
612:
613: // DECIMAL
614:
615: fsb.reset("[%d]").format(600);
616: test("[600]", fsb.toString());
617:
618: fsb.reset("[%5d]").format(600);
619: test("[ 600]", fsb.toString());
620:
621: fsb.reset("[%5d]").format(-600);
622: test("[ -600]", fsb.toString());
623:
624: fsb.reset("[%05d]").format(600);
625: test("[00600]", fsb.toString());
626:
627: fsb.reset("[%05d]").format(-600);
628: test("[-0600]", fsb.toString());
629:
630: fsb.reset("[%x]").format(10);
631: test("[a]", fsb.toString());
632:
633: fsb.reset("[%X]").format(10);
634: test("[A]", fsb.toString());
635:
636: fsb.reset("[%o]").format(10);
637: test("[12]", fsb.toString());
638:
639: fsb.reset("[%4X]").format(10);
640: test("[ A]", fsb.toString());
641:
642: fsb.reset("[%#4x]").format(10);
643: test("[ 0xa]", fsb.toString());
644:
645: fsb.reset("[%#4o]").format(10);
646: test("[ 012]", fsb.toString());
647:
648: fsb.reset("[%#04x]").format(10);
649: test("[0x0a]", fsb.toString());
650:
651: fsb.reset("[%#04o]").format(10);
652: test("[0012]", fsb.toString());
653:
654: fsb.reset();
655: test("[%#04o]", fsb.toString());
656:
657: }
658:
659: // */
660: }
|