/*
* The Unified Mapping Platform (JUMP) is an extensible, interactive GUI
* for visualizing and manipulating spatial features with geometry and attributes.
*
* Copyright (C) 2003 Vivid Solutions
*
* This program 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.
*
* This program 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. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* For more information, contact:
*
* Vivid Solutions
* Suite #1A
* 2328 Government Street
* Victoria BC V8T 5G5
* Canada
*
* (250)385-6040
* www.vividsolutions.com
*/
package com.vividsolutions.jump.util;
// Fmt - some simple single-arg sprintf-like routines
//
// Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// Visit the ACME Labs Java page for up-to-date versions of this and other
// fine Java utilities: http://www.acme.com/java/
/// Some simple single-arg sprintf-like routines.
// <P>
// 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.
// <P>
// However, using the built-in string concatenation, it's almost as
// convenient to make a series of single-argument formatting routines.
// <P>
// Fmt can format the following types:
// <BLOCKQUOTE><CODE>
// byte short int long float double char String Object
// </CODE></BLOCKQUOTE>
// For each type there is a set of overloaded methods, each returning
// a formatted String. There's the plain formatting version:
// <BLOCKQUOTE><PRE>
// Fmt.fmt( x )
// </PRE></BLOCKQUOTE>
// There's a version specifying a minimum field width:
// <BLOCKQUOTE><PRE>
// Fmt.fmt( x, minWidth )
// </PRE></BLOCKQUOTE>
// And there's a version that takes flags:
// <BLOCKQUOTE><PRE>
// Fmt.fmt( x, minWidth, flags )
// </PRE></BLOCKQUOTE>
// Currently available flags are:
// <BLOCKQUOTE><PRE>
// Fmt.ZF - zero-fill
// Fmt.LJ - left justify
// Fmt.HX - hexadecimal
// Fmt.OC - octal
// </PRE></BLOCKQUOTE>
// The HX and OC flags imply unsigned output.
// <P>
// For doubles and floats, there's a significant-figures parameter before
// the flags:
// <BLOCKQUOTE><PRE>
// Fmt.fmt( d )
// Fmt.fmt( d, minWidth )
// Fmt.fmt( d, minWidth, sigFigs )
// Fmt.fmt( d, minWidth, sigFigs, flags )
// </PRE></BLOCKQUOTE>
// <P>
// <A HREF="/resources/classes/Acme/Fmt.java">Fetch the software.</A><BR>
// <A HREF="/resources/classes/Acme.tar.gz">Fetch the entire Acme package.</A>
// <HR>
// Similar classes:
// <UL>
// <LI> Andrew Scherpbier's <A HREF="http://www.sdsu.edu/doc/java-SDSU/sdsu.FormatString.html">FormatString</A>
// Tries to allow variable numbers of arguments by
// supplying overloaded routines with different combinations of parameters,
// but doesn't actually supply that many. The floating point conversion
// is described as "very incomplete".
// <LI> Core Java's <A HREF="http://www.apl.jhu.edu/~hall/java/CoreJava-Format.html">Format</A>.
// The design seems a little weird. They want you to create an instance,
// passing the format string to the constructor, and then call an instance
// method with your data to do the actual formatting. The extra steps are
// pointless; better to just use static methods.
// </UL>
public class Fmt {
// Flags.
/// Zero-fill.
public static final int ZF = 1;
/// Left justify.
public static final int LJ = 2;
/// Hexadecimal.
public static final int HX = 4;
/// Octal.
public static final int OC = 8;
// Was a number - internal use.
private static final int WN = 16;
// byte
public static String fmt(byte b) {
return fmt(b, 0, 0);
}
public static String fmt(byte b, int minWidth) {
return fmt(b, minWidth, 0);
}
public static String fmt(byte b, int minWidth, int flags) {
boolean hexadecimal = ((flags & HX) != 0);
boolean octal = ((flags & OC) != 0);
if (hexadecimal) {
return fmt(Integer.toString(b & 0xff, 16), minWidth, flags | WN);
} else if (octal) {
return fmt(Integer.toString(b & 0xff, 8), minWidth, flags | WN);
} else {
return fmt(Integer.toString(b & 0xff), minWidth, flags | WN);
}
}
// short
public static String fmt(short s) {
return fmt(s, 0, 0);
}
public static String fmt(short s, int minWidth) {
return fmt(s, minWidth, 0);
}
public static String fmt(short s, int minWidth, int flags) {
boolean hexadecimal = ((flags & HX) != 0);
boolean octal = ((flags & OC) != 0);
if (hexadecimal) {
return fmt(Integer.toString(s & 0xffff, 16), minWidth, flags | WN);
} else if (octal) {
return fmt(Integer.toString(s & 0xffff, 8), minWidth, flags | WN);
} else {
return fmt(Integer.toString(s), minWidth, flags | WN);
}
}
// int
public static String fmt(int i) {
return fmt(i, 0, 0);
}
public static String fmt(int i, int minWidth) {
return fmt(i, minWidth, 0);
}
public static String fmt(int i, int minWidth, int flags) {
boolean hexadecimal = ((flags & HX) != 0);
boolean octal = ((flags & OC) != 0);
if (hexadecimal) {
return fmt(Long.toString(i & 0xffffffffL, 16), minWidth, flags |
WN);
} else if (octal) {
return fmt(Long.toString(i & 0xffffffffL, 8), minWidth, flags | WN);
} else {
return fmt(Integer.toString(i), minWidth, flags | WN);
}
}
// long
public static String fmt(long l) {
return fmt(l, 0, 0);
}
public static String fmt(long l, int minWidth) {
return fmt(l, minWidth, 0);
}
public static String fmt(long l, int minWidth, int flags) {
boolean hexadecimal = ((flags & HX) != 0);
boolean octal = ((flags & OC) != 0);
if (hexadecimal) {
if ((l & 0xf000000000000000L) != 0) {
return fmt(Long.toString(l >>> 60, 16) +
fmt(l & 0x0fffffffffffffffL, 15, HX | ZF), minWidth,
flags | WN);
} else {
return fmt(Long.toString(l, 16), minWidth, flags | WN);
}
} else if (octal) {
if ((l & 0x8000000000000000L) != 0) {
return fmt(Long.toString(l >>> 63, 8) +
fmt(l & 0x7fffffffffffffffL, 21, OC | ZF), minWidth,
flags | WN);
} else {
return fmt(Long.toString(l, 8), minWidth, flags | WN);
}
} else {
return fmt(Long.toString(l), minWidth, flags | WN);
}
}
// float
public static String fmt(float f) {
return fmt(f, 0, 0, 0);
}
public static String fmt(float f, int minWidth) {
return fmt(f, minWidth, 0, 0);
}
public static String fmt(float f, int minWidth, int sigFigs) {
return fmt(f, minWidth, sigFigs, 0);
}
public static String fmt(float f, int minWidth, int sigFigs, int flags) {
if (sigFigs != 0) {
return fmt(sigFigFix(Float.toString(f), sigFigs), minWidth,
flags | WN);
} else {
return fmt(Float.toString(f), minWidth, flags | WN);
}
}
// double
public static String fmt(double d) {
return fmt(d, 0, 0, 0);
}
public static String fmt(double d, int minWidth) {
return fmt(d, minWidth, 0, 0);
}
public static String fmt(double d, int minWidth, int sigFigs) {
return fmt(d, minWidth, sigFigs, 0);
}
public static String fmt(double d, int minWidth, int sigFigs, int flags) {
if (sigFigs != 0) {
return fmt(sigFigFix(doubleToString(d), sigFigs), minWidth,
flags | WN);
} else {
return fmt(doubleToString(d), minWidth, flags | WN);
}
}
// char
public static String fmt(char c) {
return fmt(c, 0, 0);
}
public static String fmt(char c, int minWidth) {
return fmt(c, minWidth, 0);
}
public static String fmt(char c, int minWidth, int flags) {
// return fmt( Character.toString( c ), minWidth, flags );
// Character currently lacks a static toString method. Workaround
// is to make a temporary instance and use the instance toString.
return fmt(new Character(c).toString(), minWidth, flags);
}
// Object
public static String fmt(Object o) {
return fmt(o, 0, 0);
}
public static String fmt(Object o, int minWidth) {
return fmt(o, minWidth, 0);
}
public static String fmt(Object o, int minWidth, int flags) {
return fmt(o.toString(), minWidth, flags);
}
// String
public static String fmt(String s) {
return fmt(s, 0, 0);
}
public static String fmt(String s, int minWidth) {
return fmt(s, minWidth, 0);
}
public static String fmt(String s, int minWidth, int flags) {
int len = s.length();
boolean zeroFill = ((flags & ZF) != 0);
boolean leftJustify = ((flags & LJ) != 0);
boolean hexadecimal = ((flags & HX) != 0);
boolean octal = ((flags & OC) != 0);
boolean wasNumber = ((flags & WN) != 0);
if ((hexadecimal || octal || zeroFill) && !wasNumber) {
throw new InternalError("Acme.Fmt: number flag on a non-number");
}
if (zeroFill && leftJustify) {
throw new InternalError("Acme.Fmt: zero-fill left-justify is silly");
}
if (hexadecimal && octal) {
throw new InternalError("Acme.Fmt: can't do both hex and octal");
}
if (len >= minWidth) {
return s;
}
int fillWidth = minWidth - len;
StringBuffer fill = new StringBuffer(fillWidth);
for (int i = 0; i < fillWidth; ++i)
if (zeroFill) {
fill.append('0');
} else {
fill.append(' ');
}
if (leftJustify) {
return s + fill;
} else if (zeroFill && s.startsWith("-")) {
return "-" + fill + s.substring(1);
} else {
return fill + s;
}
}
// Internal routines.
private static String sigFigFix(String s, int sigFigs) {
// First dissect the floating-point number string into sign,
// integer part, fraction part, and exponent.
String sign;
String unsigned;
if (s.startsWith("-") || s.startsWith("+")) {
sign = s.substring(0, 1);
unsigned = s.substring(1);
} else {
sign = "";
unsigned = s;
}
String mantissa;
String exponent;
int eInd = unsigned.indexOf('e');
if (eInd == -1) { // it may be 'e' or 'E'
eInd = unsigned.indexOf('E');
}
if (eInd == -1) {
mantissa = unsigned;
exponent = "";
} else {
mantissa = unsigned.substring(0, eInd);
exponent = unsigned.substring(eInd);
}
StringBuffer number;
StringBuffer fraction;
int dotInd = mantissa.indexOf('.');
if (dotInd == -1) {
number = new StringBuffer(mantissa);
fraction = new StringBuffer("");
} else {
number = new StringBuffer(mantissa.substring(0, dotInd));
fraction = new StringBuffer(mantissa.substring(dotInd + 1));
}
int numFigs = number.length();
int fracFigs = fraction.length();
if (((numFigs == 0) || number.equals("0")) && (fracFigs > 0)) {
// Don't count leading zeros in the fraction.
numFigs = 0;
for (int i = 0; i < fraction.length(); ++i) {
if (fraction.charAt(i) != '0') {
break;
}
--fracFigs;
}
}
int mantFigs = numFigs + fracFigs;
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)));
// Round?
} else if (sigFigs < numFigs) {
// Want fewer figures in the number; turn them to zeros.
fraction.setLength(0); // should already be zero, but make sure
for (int i = sigFigs; i < numFigs; ++i)
number.setCharAt(i, '0');
// Round?
}
// Else sigFigs == mantFigs, which is fine.
if (fraction.length() == 0) {
return sign + number + exponent;
} else {
return sign + number + "." + fraction + exponent;
}
}
/// Improved version of Double.toString(), returns more decimal places.
// <P>
// The JDK 1.0.2 version of Double.toString() returns only six decimal
// places on some systems. In JDK 1.1 full precision is returned on
// all platforms.
// @deprecated
// @see java.lang.Double#toString
public static String doubleToString(double d) {
// Handle special numbers first, to avoid complications.
if (Double.isNaN(d)) {
return "NaN";
}
if (d == Double.NEGATIVE_INFINITY) {
return "-Inf";
}
if (d == Double.POSITIVE_INFINITY) {
return "Inf";
}
// Grab the sign, and then make the number positive for simplicity.
boolean negative = false;
if (d < 0.0D) {
negative = true;
d = -d;
}
// Get the native version of the unsigned value, as a template.
String unsStr = Double.toString(d);
// Dissect out the exponent.
String mantStr;
// Dissect out the exponent.
String expStr;
int exp;
int eInd = unsStr.indexOf('e');
if (eInd == -1) { // it may be 'e' or 'E'
eInd = unsStr.indexOf('E');
}
if (eInd == -1) {
mantStr = unsStr;
expStr = "";
exp = 0;
} else {
mantStr = unsStr.substring(0, eInd);
expStr = unsStr.substring(eInd + 1);
if (expStr.startsWith("+")) {
exp = Integer.parseInt(expStr.substring(1));
} else {
exp = Integer.parseInt(expStr);
}
}
// Dissect out the number part.
String numStr;
int dotInd = mantStr.indexOf('.');
if (dotInd == -1) {
numStr = mantStr;
} else {
numStr = mantStr.substring(0, dotInd);
}
long num;
if (numStr.length() == 0) {
num = 0;
} else {
num = Integer.parseInt(numStr);
}
// Build the new mantissa.
StringBuffer newMantBuf = new StringBuffer(numStr + ".");
double p = Math.pow(10, exp);
double frac = d - (num * p);
String digits = "0123456789";
int nDigits = 16 - numStr.length(); // about 16 digits in a double
for (int i = 0; i < nDigits; ++i) {
p /= 10.0D;
int dig = (int) (frac / p);
if (dig < 0) {
dig = 0;
}
if (dig > 9) {
dig = 9;
}
newMantBuf.append(digits.charAt(dig));
frac -= (dig * p);
}
if ((int) ((frac / p) + 0.5D) == 1) {
// Round up.
boolean roundMore = true;
for (int i = newMantBuf.length() - 1; i >= 0; --i) {
int dig = digits.indexOf(newMantBuf.charAt(i));
if (dig == -1) {
continue;
}
++dig;
if (dig == 10) {
newMantBuf.setCharAt(i, '0');
continue;
}
newMantBuf.setCharAt(i, digits.charAt(dig));
roundMore = false;
break;
}
if (roundMore) {
// If this happens, we need to prepend a 1. But I haven't
// found a test case yet, so I'm leaving it out for now.
// But if you get this message, please let me know!
newMantBuf.append("ROUNDMORE");
}
}
// Chop any trailing zeros.
int len = newMantBuf.length();
while (newMantBuf.charAt(len - 1) == '0')
newMantBuf.setLength(--len);
// And chop a trailing dot, if any.
if (newMantBuf.charAt(len - 1) == '.') {
newMantBuf.setLength(--len);
}
// Done.
return (negative ? "-" : "") + newMantBuf +
((expStr.length() != 0) ? ("e" + expStr) : "");
}
/******************************************************************************
/// Test program.
public static void main( String[] args )
{
System.out.println( "Starting tests." );
show( Fmt.fmt( "Hello there." ) );
show( Fmt.fmt( 123 ) );
show( Fmt.fmt( 123, 10 ) );
show( Fmt.fmt( 123, 10, Fmt.ZF ) );
show( Fmt.fmt( 123, 10, Fmt.LJ ) );
show( Fmt.fmt( -123 ) );
show( Fmt.fmt( -123, 10 ) );
show( Fmt.fmt( -123, 10, Fmt.ZF ) );
show( Fmt.fmt( -123, 10, Fmt.LJ ) );
show( Fmt.fmt( (byte) 0xbe, 22, Fmt.OC ) );
show( Fmt.fmt( (short) 0xbabe, 22, Fmt.OC ) );
show( Fmt.fmt( 0xcafebabe, 22, Fmt.OC ) );
show( Fmt.fmt( 0xdeadbeefcafebabeL, 22, Fmt.OC ) );
show( Fmt.fmt( 0x8000000000000000L, 22, Fmt.OC ) );
show( Fmt.fmt( (byte) 0xbe, 16, Fmt.HX ) );
show( Fmt.fmt( (short) 0xbabe, 16, Fmt.HX ) );
show( Fmt.fmt( 0xcafebabe, 16, Fmt.HX ) );
show( Fmt.fmt( 0xdeadbeefcafebabeL, 16, Fmt.HX ) );
show( Fmt.fmt( 0x8000000000000000L, 16, Fmt.HX ) );
show( Fmt.fmt( 'c' ) );
show( Fmt.fmt( new java.util.Date() ) );
show( Fmt.fmt( 123.456F ) );
show( Fmt.fmt( 123456000000000000.0F ) );
show( Fmt.fmt( 123.456F, 0, 8 ) );
show( Fmt.fmt( 123.456F, 0, 7 ) );
show( Fmt.fmt( 123.456F, 0, 6 ) );
show( Fmt.fmt( 123.456F, 0, 5 ) );
show( Fmt.fmt( 123.456F, 0, 4 ) );
show( Fmt.fmt( 123.456F, 0, 3 ) );
show( Fmt.fmt( 123.456F, 0, 2 ) );
show( Fmt.fmt( 123.456F, 0, 1 ) );
show( Fmt.fmt( 123456000000000000.0F, 0, 4 ) );
show( Fmt.fmt( -123.456F, 0, 4 ) );
show( Fmt.fmt( -123456000000000000.0F, 0, 4 ) );
show( Fmt.fmt( 123.0F ) );
show( Fmt.fmt( 123.0D ) );
show( Fmt.fmt( 1.234567890123456789F ) );
show( Fmt.fmt( 1.234567890123456789D ) );
show( Fmt.fmt( 1234567890123456789F ) );
show( Fmt.fmt( 1234567890123456789D ) );
show( Fmt.fmt( 0.000000000000000000001234567890123456789F ) );
show( Fmt.fmt( 0.000000000000000000001234567890123456789D ) );
show( Fmt.fmt( 12300.0F ) );
show( Fmt.fmt( 12300.0D ) );
show( Fmt.fmt( 123000.0F ) );
show( Fmt.fmt( 123000.0D ) );
show( Fmt.fmt( 1230000.0F ) );
show( Fmt.fmt( 1230000.0D ) );
show( Fmt.fmt( 12300000.0F ) );
show( Fmt.fmt( 12300000.0D ) );
show( Fmt.fmt( Float.NaN ) );
show( Fmt.fmt( Float.POSITIVE_INFINITY ) );
show( Fmt.fmt( Float.NEGATIVE_INFINITY ) );
show( Fmt.fmt( Double.NaN ) );
show( Fmt.fmt( Double.POSITIVE_INFINITY ) );
show( Fmt.fmt( Double.NEGATIVE_INFINITY ) );
show( Fmt.fmt( 1.0F / 8.0F ) );
show( Fmt.fmt( 1.0D / 8.0D ) );
System.out.println( "Done with tests." );
}
private static void show( String str )
{
System.out.println( "#" + str + "#" );
}
******************************************************************************/
}