// F.java ///////////////////////////////////////////////////////////////////
//
// Based heavily on the Fmt class by Jef Poskanzer <jef@acme.com>
//
// Visit his ACME Labs Java page at http://www.acme.com/java/
//
package com.informagen;
// Doing fprintf-like formatting in Java
//
// It is apparently impossible to declare a Java method that accepts
// variable numbers of any type of argument. You can declare it to take
// Objects, but numeric variables and constants are not in fact Objects.
//
// However, using the built-in string concatenation, it's almost as
// convenient to make a series of single-argument formatting routines.
//
// Class F can format the following datatypes:
//
// boolean, byte, char, short, int, long, float, double,
// Boolean, Byte, Character, Short, Integer, Long, Float,
// Date, Color, StringBuffer, String, and Object
//
// For each data type there is a set of overloaded methods, each returning
// a formatted String. There's the plain formatting version:
//
// F.f(x)
//
//
// There's a version specifying a field width:
//
// F.f(x, width)
//
//
// There's a version which takes formatting flags:
//
// F.f(x, flags)
//
//
// And there's a version that takes both:
//
// F.f(x, width, flags)
//
//
// Currently available flags are:
//
// F.RJ - right justify (the default)
// F.LJ - left justify
// F.CJ - center justify
// F.ZF - zero fill
// F.RF - repeat fill
// F.HX - hexadecimal
// F.OC - octal
// F.BN - binary
// F.LC - lowercase
// F.UC - uppercase
// F.XC - OK to exceed field width
// F.TR - truncate string to width
//
// Right justify is the default for all formatting.
//
// Center justify must be used with a width and will not work with
// zero or repeat fill or left justify. In these cases it is ignored.
//
// The HX, OC and BN flags produce unsigned output. Hexidecimal representation
// have an "0x" prefix and octal representation have a "0" prefix.
//
// Repeat fill is useful for making string of the same character
//
// Uppercase and lowecase can be used on numbers which contain letter ie Hexadecimal
// or exponential.
//
// Use XC when the field width isn't equal to zero ie "DEFAULT" but you don't might if
// the value exceed its field boundaries.
//
// Use TR with care with numeric fields as the output could be misinterpreted. XC will
// override TR.
//
//
// For formatting dates there are a set of predefined format strings for convienence.
// See the documentation for the class "java.text.SimpleDateFormat" for an extensive
// description of the format used in date templates.
//
// F.SYBASEDATE = "MMM dd yyyy hh:mm:ss:SSSaa" Sybase datetime stamp
// F.DATE = "dd-MMM-yyyy" Three character month
// F.TIME = "hh:mmaa" AM/PM 12 hour time
// F.MILTIME = "HHmm" 24 hour time ie Military
// F.MONTH = "MMMM" Full month name
// F.WEEKDAY = "EEEE" Full weekday name
//
//
// For real numbers, ie doubles and floats, there's a significant-figures parameter.
//
// F.f(d, width, decimalDigits)
//
//
// If the value of decimalDigits is negative the value will be treated as the number of
// significant figures to be printed. Significant figures are used in scientific calculations
// in order to express intermediate and final calculation with the proper significance
// from the measured values.
//
//
public class F {
// Flags ----------------------------------------------------------------------
public static final int DEFAULT = Integer.MAX_VALUE; // Use the default width/sigfigs
public static final short RJ = 1; // Right justify
public static final short LJ = 2; // Left justify
public static final short CJ = 4; // Center justify
public static final short ZF = 8; // Zero-fill
public static final short RF = 16; // Repeat-fill
public static final short HX = 32; // Hexadecimal
public static final short OC = 64; // Octal
public static final short BN = 128; // Binary
public static final short UC = 256; // Uppercase
public static final short LC = 512; // Lowercase
public static final short XC = 1024; // OK to exceed field width
public static final short TR = 2048; // Truncate string to width
// Convienent date/time templates --------------------------------------------
// Re: "java.text.SimpleDateFormat" for a guide to using the symbols
public static final String SYBASEDATE = "MMM dd yyyy hh:mm:ss:SSSaa"; // Sybase datetime stamp
public static final String DATE = "dd-MMM-yyyy"; // Three character month
public static final String TIME = "hh:mmaa"; // AM/PM 12 hour time
public static final String MILTIME = "HHmm"; // 24 hour time ie Military
public static final String MONTH = "MMMM"; // Full month name
public static final String WEEKDAY = "EEEE"; // Full weekday name
// boolean --------------------------------------------------------------------
public static String f(boolean b) { return f(b, DEFAULT, RJ); }
public static String f(boolean b, short flags) { return f(b, DEFAULT, flags); }
public static String f(boolean b, int width) { return f(b, width, RJ); }
public static String f(boolean b, int width, short flags) {
if (((flags & HX) != 0 ) | ((flags & OC) != 0 ) | ((flags & BN) != 0 ))
return f((b) ? (byte)1 : (byte)0, width, flags);
else
return f((b) ? "true" : "false", width, flags);
}
// byte -----------------------------------------------------------------------
public static String f(byte b) { return f(b, DEFAULT, RJ); }
public static String f(byte b, short flags) { return f(b, DEFAULT, flags); }
public static String f(byte b, int width) { return f(b, width, RJ); }
public static String f(byte b, int width, short flags) {
if (((flags & HX) != 0 ) | ((flags & OC) != 0 ) | ((flags & BN) != 0 ))
return f((b&0xff), width, flags);
else
return f(Integer.toString(b&0xff), width, flags);
}
// char -----------------------------------------------------------------------
public static String f(char c) { return f(c, DEFAULT, RJ); }
public static String f(char c, short flags) { return f(c, DEFAULT, flags); }
public static String f(char c, int width) { return f(c, width, RJ); }
public static String f(char c, int width, short flags) {
boolean hexadecimal = ((flags & HX) != 0);
boolean octal = ((flags & OC) != 0);
boolean binary = ((flags & BN) != 0);
if (hexadecimal)
return f("0x" + f(Integer.toHexString(c & 0xffff), 4, ZF), width, flags);
else if (octal | binary)
return f((short)c, width, flags);
else
return f(new Character(c).toString(), width, flags);
}
// short -----------------------------------------------------------------------
public static String f(short s) {return f(s, DEFAULT, RJ); }
public static String f(short s, short flags) {return f(s, DEFAULT, flags); }
public static String f(short s, int width) { return f(s, width, RJ); }
public static String f(short s, int width, short flags) {
if (((flags & HX) != 0 ) | ((flags & OC) != 0 ) | ((flags & BN) != 0 ))
return f((s & 0xffff), width, flags);
else
return f(Integer.toString(s), width, flags);
}
// int -----------------------------------------------------------------------
public static String f(int i) { return f(i, DEFAULT, RJ); }
public static String f(int i, short flags) { return f(i, DEFAULT, flags); }
public static String f(int i, int width) { return f(i, width, RJ); }
public static String f(int i, int width, short flags) {
if (((flags & HX) != 0 ) | ((flags & OC) != 0 ) | ((flags & BN) != 0 ))
return f((i & 0xffffffffL), width, flags );
else
return f(Integer.toString(i), width, flags);
}
// long -----------------------------------------------------------------------
public static String f(long l) { return f(l, DEFAULT, RJ); }
public static String f(long l, short flags) { return f(l, DEFAULT, flags); }
public static String f(long l, int width) { return f(l, width, RJ ); }
public static String f(long l, int width, short flags) {
boolean hexadecimal = ((flags & HX) != 0 );
boolean octal = ((flags & OC) != 0 );
boolean binary = ((flags & BN) != 0 );
if (hexadecimal)
return f("0x" + Long.toHexString(l), width, flags);
else if (octal)
return f("0" + Long.toOctalString(l), width, flags);
else if (binary)
return f(Long.toBinaryString(l), width, flags);
else
return f(Long.toString(l), width, flags);
}
// Float and float -----------------------------------------------------------------------
public static String f(Float f, int width, int decimalDigits, short flags) {
return ( f == null ) ? f("null", width, flags) : f(f.floatValue(), width, decimalDigits, flags);
}
public static String f(float f) { return f(f, DEFAULT, DEFAULT, RJ); }
public static String f(float f, short flags) { return f(f, DEFAULT, DEFAULT, flags); }
public static String f(float f, int width) { return f(f, width, DEFAULT, RJ); }
public static String f(float f, int width, short flags) { return f(f, width, DEFAULT, flags); }
public static String f(float f, int width, int decimalDigits) { return f(f, width, decimalDigits, RJ); }
public static String f(float f, int width, int decimalDigits, short flags) {
if (((flags & HX) != 0 ) | ((flags & OC) != 0 ) | ((flags & BN) != 0 ))
return f(Float.floatToIntBits(f), width, flags);
if ( decimalDigits == DEFAULT )
return f(Float.toString( f ), width, (short)flags);
else
return f(sigFig(Float.toString(f), decimalDigits), width, flags);
}
// Double and double -----------------------------------------------------------------------
public static String f(Double d, int width, int decimalDigits, short flags) {
return ( d == null ) ? f("null", width, flags) : f(d.doubleValue(), width, decimalDigits, flags);
}
public static String f(double d) { return f(d, DEFAULT, DEFAULT, RJ); }
public static String f(double d, short flags) { return f(d, DEFAULT, DEFAULT, flags); }
public static String f(double d, int width) { return f(d, width, DEFAULT, RJ); }
public static String f(double d, int width, short flags) { return f(d, width, DEFAULT, flags); }
public static String f(double d, int width, int decimalDigits) { return f(d, width, decimalDigits, RJ); }
public static String f(double d, int width, int decimalDigits, short flags) {
if (((flags & HX) != 0 ) | ((flags & OC) != 0 ) | ((flags & BN) != 0 ))
return f(Double.doubleToLongBits(d), width, flags);
if ( decimalDigits == DEFAULT )
return f(Double.toString(d), width, flags);
else
return f(sigFig(Double.toString(d), decimalDigits), width, flags );
}
// Date -----------------------------------------------------------------------
public static String f(java.util.Date d, String template) { return f(d, template, RJ); }
public static String f(java.util.Date d, String template, int width) { return f(d, template, width, RJ); }
public static String f(java.util.Date d, String template, short flags) { return f(d, template, DEFAULT, flags); }
public static String f(java.util.Date d, String template, int width, short flags) {
if ( d == null )
return f("null", width, flags);
else {
java.text.SimpleDateFormat dateFormatter = new java.text.SimpleDateFormat(template);
return f(dateFormatter.format(d), width, flags);
}
}
// Color -----------------------------------------------------------------------
public static String f(java.awt.Color c, int width, short flags) {
if ( c == null)
return f("null", width, flags);
else {
short binHexFlags = (short)((flags & HX) | (flags & OC) | (flags & BN));
return f(f(c.getRed(), DEFAULT, binHexFlags) + "," +
f(c.getGreen(), DEFAULT, binHexFlags)+ "," +
f(c.getBlue(), DEFAULT, binHexFlags), width, flags);
}
}
// Object -----------------------------------------------------------------------
public static String f(Object o) { return f(o, DEFAULT, RJ); }
public static String f(Object o, int width) { return f(o, width, RJ); }
public static String f(Object o, short flags) { return f(o, DEFAULT, flags); }
public static String f(Object o, int width, short flags) {
if ( o == null )
return f("null", width, flags);
else {
if (o instanceof java.lang.String)
return f((String)o, width, flags);
else if (o instanceof java.lang.Boolean)
return f(((Boolean)o).booleanValue(), width, flags);
else if (o instanceof java.lang.Byte)
return f(((Byte)o).byteValue(), width, flags);
else if (o instanceof java.lang.Character)
return f(((Character)o).charValue(), width, flags);
else if (o instanceof java.lang.Short)
return f(((Short)o).shortValue(), width, flags);
else if (o instanceof java.lang.Integer)
return f(((Integer)o).intValue(), width, flags);
else if (o instanceof java.lang.Long)
return f(((Long)o).longValue(), width, flags);
else if (o instanceof java.lang.Float)
return f(((Float)o).floatValue(), width, flags);
else if (o instanceof java.lang.Double)
return f(((Double)o).doubleValue(), width, flags);
else if (o instanceof java.lang.StringBuffer)
return f((java.lang.StringBuffer)o, width, flags);
else if (o instanceof java.awt.Color)
return f((java.awt.Color)o, width, flags);
else
return f(o.toString(), width, flags);
}
}
// StringBuffer ------------------------------------------------------------------
public static String f(StringBuffer sb, int width, short flags) {
return ( sb == null) ? f("null", width, flags) : f(sb.toString(), width, flags);
}
// String ------------------------------------------------------------------------
//
// String overloaded methods could be dispatched via the Object however
// as they are heavily used they are explicitly declared.
public static String f(String s, int width, short flags) {
width = (width < 0) ? -width : width;
if ( s == null )
return f("null", width, flags);
int len = s.length();
boolean zeroFill = ((flags & ZF) != 0);
boolean repeatFill = ((flags & RF) != 0);
boolean rightJustify = ((flags & RJ) != 0);
boolean leftJustify = ((flags & LJ) != 0);
boolean centerJustify = ((flags & CJ) != 0);
boolean upperCase = ((flags & UC) != 0);
boolean lowerCase = ((flags & LC) != 0);
boolean okToExceed = ((flags & XC) != 0);
boolean truncate = ((flags & TR) != 0);
// Width is zero and we are not allowed to exceed it
if ( width == 0 && !okToExceed )
return "";
// Width is not zero but the string is too big and we are not allowed to
// exceed the width or truncate the output, print a string of asterisks instead.
if ( (width != DEFAULT) && (width < len) && (okToExceed == false) && (truncate == false) )
return f("*", width, RF);
// No special formatting to be done, this is the usual case so it
// is worthwhile taking the time to test here and just returning.
if ( (width <= len || width == DEFAULT) &&
!zeroFill &&
!repeatFill &&
!centerJustify &&
!upperCase &&
!lowerCase &&
!truncate)
return s;
// Determine reasonable behavior from conflicting flags
if ( rightJustify && (leftJustify || centerJustify) )
rightJustify = false;
if ( centerJustify && (repeatFill || zeroFill || leftJustify) )
centerJustify = false;
if ( repeatFill && zeroFill )
repeatFill = false;
if ( truncate && (okToExceed || (width == DEFAULT)) )
truncate = false;
// Zero and repeat fill, leading spaces
int fillWidth = 0;
if ( width != DEFAULT )
fillWidth = (repeatFill) ? width : width - len;
fillWidth = (fillWidth < 0) ? 0 : fillWidth;
StringBuffer buffer = new StringBuffer(fillWidth + len);
int i=0;
while ( i<fillWidth ) {
if ( zeroFill )
buffer.append('0');
else if ( repeatFill )
buffer.append(s.charAt(i%len));
else
buffer.append(' ');
i++;
}
String fill = buffer.toString();
// Assemble the final string using the fill string created above
buffer.setLength(0);
if ( leftJustify ) {
buffer.append(s).append(fill);
} else if ( centerJustify ) {
buffer.append(fill.substring(0,fillWidth/2)).append(s).append(fill.substring(fillWidth/2));
} else if ( zeroFill && s.startsWith( "-" ) )
buffer.append("-").append(fill).append(s.substring(1));
else if ( repeatFill )
buffer.append(fill);
else
buffer.append(fill).append(s);
// Truncate
if ( truncate && width != DEFAULT )
buffer.setLength(width);
// Deal with the case flags
if ( upperCase )
return buffer.toString().toUpperCase();
else if ( lowerCase )
return buffer.toString().toLowerCase();
else
return buffer.toString();
}
//-------------------------------------------------------------------------------------------
// Internal routine, sigFig.
private static String sigFig(String s, int decimalDigits) {
// First dissect the floating-point number string into optional sign,
// integer part, fraction part, and optional exponent.
// Sign - may be '-' or '+' or nothing
String sign;
String unsigned;
if ( s.startsWith( "-" ) || s.startsWith( "+" )) {
sign = s.substring(0, 1);
unsigned = s.substring(1);
} else {
sign = "";
unsigned = s;
}
// Exponent - may be 'e' or 'E' format
String mantissa;
String exponent;
int eIndex = unsigned.indexOf('e');
if ( eIndex == -1 )
eIndex = unsigned.indexOf('E');
if ( eIndex == -1) {
mantissa = unsigned;
exponent = "";
} else {
mantissa = unsigned.substring(0, eIndex);
exponent = unsigned.substring(eIndex);
}
// Number and fraction
StringBuffer number, fraction;
int dotIndex = mantissa.indexOf('.');
if ( dotIndex == -1) {
number = new StringBuffer(mantissa);
fraction = new StringBuffer("");
} else {
number = new StringBuffer(mantissa.substring(0, dotIndex));
fraction = new StringBuffer(mantissa.substring(dotIndex + 1));
}
int numFigs = number.length();
int fracFigs = fraction.length();
// Don't count leading zeros in the fraction.
if ((numFigs == 0 || number.equals("0")) && fracFigs > 0) {
numFigs = 0;
for ( int i = 0; i < fraction.length(); ++i) {
if ( fraction.charAt( i ) != '0' )
break;
--fracFigs;
}
}
int mantFigs = numFigs + fracFigs;
// if decimalDigits is positive return significant figures otherwise treat
// decimalDigits a precision.
if ( decimalDigits < 0 || decimalDigits == DEFAULT) {
int sigFigs = -decimalDigits;
// Special case!!!
if ( decimalDigits == DEFAULT)
sigFigs = 0;
if ( sigFigs > mantFigs) { // We want more figures; just append zeros to the fraction.
for ( int i = mantFigs; i < sigFigs; ++i )
fraction.append( '0' );
} else if ( sigFigs < mantFigs && sigFigs >= numFigs) { // Want fewer figures in the fraction; chop.
fraction.setLength(fraction.length() - ( fracFigs - ( sigFigs - numFigs ) ) );
} else if ( sigFigs < numFigs) { // Want fewer figures in the number; turn them to zeros.
fraction.setLength(0);
for ( int i = sigFigs; i < numFigs; ++i )
number.setCharAt( i, '0' );
}
if ( fraction.length() == 0 )
return sign + number + exponent;
else
return sign + number + "." + fraction + exponent;
} else {
// Treat sigFig as a measure of precision
if ( exponent.length()>0 && exponent.charAt(1) == '-' ) {
number.append(fraction);
fraction.setLength(0);
fraction.append(f("0", Integer.parseInt(exponent.substring(2))-1, RF)).append(number);
fracFigs = fraction.length();
exponent = "";
number.setLength(0);
number.append("0");
}
for (int i = fracFigs; i < decimalDigits; ++i )
fraction.append('0');
fraction.setLength(decimalDigits);
return sign + number + "." + fraction + exponent;
}
}
}