Web Development and Design | Tutorial for Java, PHP, HTML, Javascript: java-programming-tutorial

Web Development and Design | Tutorial for Java, PHP, HTML, Javascript: java-programming-tutorial
Showing posts with label java-programming-tutorial. Show all posts
Showing posts with label java-programming-tutorial. Show all posts

Java Converting Epoch Seconds to DMYHMS

Java Converting Epoch Seconds to DMYHMS

Problem

You need to convert a number of seconds since 1970 into a Date .

Solution

Just use the Date constructor.

Explained

“The Epoch” is the beginning of time as far as modern operating systems go. Unix
time, and some versions of Windows time, count off inexorably the seconds since
the epoch. On systems that store this in a 32-bit integer, time is indeed running out.
Let’s say we wanted to find out when the Unix operating system, whose 32-bit ver-
sions use a 32-bit date, will get into difficulty. We take a 32-bit integer of all ones,
and construct a Date around it. The Date constructor needs the number of millisec-
onds since 1970, so we multiply by 1,000:

/** When does the UNIX date get into trouble? */
public class Y2038 {
public static void main(String[] a) {
// This should yield 2038AD, the hour of doom for the
// last remaining 32-bit UNIX systems (there will be
// millions of 64-bit UNIXes by then).
long expiry = 0x7FFFFFFFL;
System.out.println("32-bit UNIX expires on " +
Long.toHexString(expiry) + " or " +
new java.util.Date(expiry * 1000));
}
}


Sure enough, the program reports that 32-bit Unixes will expire in the year 2038 (you might think I knew that in advance if you were to judge by the name I gave the class; in fact, my web site has carried the Y2038 warning to Unix users for several years now). At least Unix system managers have more warning than most of the gen- eral public had for the original Y2K problem.

> java Y2038
32-bit UNIX expires on 7fffffff or Mon Jan 18 22:14:07 EST 2038
>

At any rate, if you need to convert seconds since 1970 to a date, you know how.

Java Parsing Strings into Dates

Java Parsing Strings into Dates

Problem

You need to convert user input into Date or Calendar objects.

Solution

Use a DateFormat .

Explained

The DateFormat class introduced in Recipe 6.2 has some additional methods, notably
parse( ) , which tries to parse a string according to the format stored in the given
DateFormat object:

// DateParse1.java
SimpleDateFormat formatter
= new SimpleDateFormat ("yyyy-MM-dd");
String input = args.length == 0 ? "1818-11-11" : args[0];
System.out.print(input + " parses as ");
Date t;
try {
t = formatter.parse(input);
System.out.println(t);
} catch (ParseException e) {
System.out.println("unparseable using " + formatter);
}

This program parses any date back to Year Zero and well beyond Year 2000. What if the date is embedded in an input string? You could, of course, use the string’s substring( ) method to extract it, but there is an easier way. The ParsePosition object from java.text is designed to represent (and track) the posi- tion of an imaginary cursor in a string. Suppose we have genealogical data with input strings representing the times of a person’s life:

BD: 1913-10-01 Vancouver, B.C.
DD: 1983-06-06 Toronto, ON

This lists one person’s birth date (BD) and place, and death date (DD) and place. We can parse these using String.indexOf(' ') to find the space after the : character, DateFormat parse( ) to parse the date, and String.substring( ) to get the city and other geographic information. Here’s how:

// DateParse2.java
SimpleDateFormat formatter =
new SimpleDateFormat ("yyyy-MM-dd");
String input[] = {
"BD: 1913-10-01 Vancouver, B.C.",
"MD: 1948-03-01 Ottawa, ON",
"DD: 1983-06-06 Toronto, ON" };
for (int i=0; i<input.length; i++) {
String aLine = input[i];
String action;
switch(aLine.charAt(0)) {
case 'B': action = "Born"; break;
case 'M': action = "Married"; break;
case 'D': action = "Died"; break;
// others...
default: System.err.println("Invalid code in " + aLine);
continue;
}
int p = aLine.indexOf(' ');
ParsePosition pp = new ParsePosition(p);
Date d = formatter.parse(aLine, pp);
if (d == null) {
System.err.println("Invalid date in " + aLine);
continue;
}
String location = aLine.substring(pp.getIndex( ));
System.out.println(
action + " on " + d + " in " + location);
}

This works like I said it would:

Born on Wed Oct 01 00:00:00 PDT 1913 in Vancouver, B.C.
Married on Mon Mar 01 00:00:00 PST 1948 in Ottawa, ON
Died on Mon Jun 06 00:00:00 PDT 1983 in Toronto, ON

Note that the polymorphic form of parse( ) that takes one argument throws a ParseException if the input cannot be parsed, while the form that takes a ParsePosition as its second argument returns null to indicate failure.

Java Converting YMDHMS to a Calendar or Epoch Seconds

Java Converting YMDHMS to a Calendar
or Epoch Seconds

Problem

You have year, month, day, hour, minute, and maybe even seconds, and you need to
convert it to a Calendar or a Date .

Solution

Use the Calendar class’s set(y,m,d,h,m[,s]) method, which allows you to set the
date/time fields to whatever you wish. Note that when using this form and providing
your own numbers, or when constructing either a Date or a GregorianCalendar
object, the month value is zero-based while all the other values are true-origin. Pre-
sumably, this is to allow you to print the month name from an array without having
to remember to subtract one, but it is still confusing.

// GregCalDemo.java
GregorianCalendar d1 = new GregorianCalendar(1986, 04, 05); // May 5
GregorianCalendar d2 = new GregorianCalendar( );
// today
Calendar d3 = Calendar.getInstance( );
// today
System.out.println("It was then " + d1.getTime( ));
System.out.println("It is now " + d2.getTime( ));
System.out.println("It is now " + d3.getTime( ));
d3.set(Calendar.YEAR, 1915);
d3.set(Calendar.MONTH, Calendar.APRIL);
d3.set(Calendar.DAY_OF_MONTH, 12);
System.out.println("D3 set to " + d3.getTime( ));

This prints the dates as shown:

It was then Mon May 05 00:00:00 EDT 1986
It is now Thu Mar 25 16:36:07 EST 2004
It is now Thu Mar 25 16:36:07 EST 2004
D3 set to Mon Apr 12 16:36:07 EST 1915

Java Finding Today’s Date

Java Finding Today’s Date

Problem

You want to find today’s date.

Solution

Use a Date object’s toString( ) method.

Explained

The quick and simple way to get today’s date and time is to construct a Date object
with no arguments in the constructor call, and call its toString( ) method:

// Date0.java
System.out.println(new java.util.Date( ));

However, for reasons just outlined, we want to use a Calendar object. Just use Calendar.getInstance( ).getTime( ) , which returns a Date object (even though the name makes it seem like it should return a Time value * ) and prints the resulting Date object, using its toString( ) method or preferably a DateFormat object. You might be tempted to construct a GregorianCalendar object, using the no-argument construc- tor, but if you do this, your program will not give the correct answer when non- Western locales get Calendar subclasses of their own (which might occur in some future release of Java). The static factory method Calendar.getInstance( ) returns a localized Calendar subclass for the locale you are in. In North America and Europe it will likely return a GregorianCalendar , but in other parts of the world it might (some- day) return a different kind of Calendar . Do not try to use a GregorianCalendar ’s toString( ) method; the results are truly impressive, but not very interesting. Sun’s implementation prints all its internal state information; Kaffe’s inherits Object ’s toString( ) , which just prints the class name and the hashcode. Neither is useful for our purposes.

C> java Date1
java.util.
GregorianCalendar[time=932363506950,areFieldsSet=true,areAllFieldsSet=true,lenient=tr
ue,zone=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-
28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=3,sta
rtDay=1,startDayOfWeek=1,startTime=7200000,endMode=2,endMonth=9,endDay=-
1,endDayOfWeek=1,endTime=7200000],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEA
R=1999,MONTH=6,WEEK_OF_YEAR=30,WEEK_OF_MONTH=4,DAY_OF_MONTH=18,DAY_OF_YEAR=199,DAY_
OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=3,AM_PM=1,HOUR=10,HOUR_OF_
DAY=22,MINUTE=51,SECOND=46,MILLISECOND=950,ZONE_OFFSET=-28800000,DST_OFFSET=3600000]

Calendar ’s getTime( ) returns a Date object, which can be passed to println( ) to print today’s date (and time) in the traditional (but non-localized) format:

/ Date2.java
System.out.println(Calendar.getInstance( ).getTime( ));

Java Program: TempConverter

Java Program: TempConverter


The program shown in Example prints a table of Fahrenheit temperatures (still
used in daily life weather reporting in the United States) and the corresponding Celsius temperatures (used in science everywhere, and in daily life in most of the world).

Example TempConverter.java
import java.text.*;
/* Print a table of Fahrenheit and Celsius temperatures
*/
public class TempConverter {
public static void main(String[] args) {
TempConverter t = new TempConverter( );
t.start( );
t.data( );
t.end( );
}
protected void start( ) {
}
protected void data( ) {
for (int i=-40; i<=120; i+=10) {
float c = (i-32)*(5f/9);
print(i, c);
}
}
protected void print(float f, float c) {
System.out.println(f + " " + c);
}
protected void end( ) {
}
}


This works, but these numbers print with about 15 digits of (useless) decimal frac- tions! The second version of this program subclasses the first and uses a DecimalFormat to control the formatting of the converted temperatures

Example TempConverter2.java
import java.text.*;
/* Print a table of fahrenheit and celsius temperatures, a bit more neatly.
*/
public class TempConverter2 extends TempConverter {
protected DecimalFormat df;
public static void main(String[] args) {
TempConverter t = new TempConverter2( );
t.start( );
t.data( );
t.end( );
}
// Constructor
public TempConverter2( ) {
df = new DecimalFormat("#0.00");
}
protected void print(float f, float c) {
System.out.println(f + " " + df.format(c));
}
protected void start( ) {
System.out.println("Fahr
}
Centigrade.");
protected void end( ) {
System.out.println("-------------------");
}
}

This works, and the results are better than the first version’s, but still not right:

C:\javasrc\numbers>java
Fahr
Centigrade.
-40.00 -40.00
-30.00 -34.44
-20.00 -28.89
-10.00 -23.33
0.00 -17.78
10.00 -12.22
20.00 -6.67
30.00 -1.11
40.00 4.44
50.00 10.00
60.00 15.56
70.00 21.11
80.00 26.67
90.00 32.22
100.00 37.78
110.00 43.33
120.00 48.89
TempConverter2


It would look neater if we lined up the decimal points, but Java had nothing in its standard API for doing this. This is deliberate! They wanted to utterly break the ties with the ancient IBM 1403 line printers and similar monospaced devices such as typewriters, “dumb” terminals, * and DOS terminal windows. However, with a bit of simple arithmetic, the FieldPosition from Recipe 5.11 can be used to figure out how many spaces need to be prepended to line up the columns; the arithmetic is done in print( ) , and the spaces are put on in prependSpaces( ) . The result is much prettier:

C:\javasrc\numbers>java
Fahr
Centigrade.
-40
-40
-30
-34.444
-20
-28.889
-10
-23.333
0
-17.778
10
-12.222
20
-6.667
TempConverter30
30
-1.111
40
4.444
50
10
60
15.556
70
21.111
80
26.667
90
32.222
100
37.778
110
43.333
120
48.889
-------------------

And the code is only ten lines longer!

import java.text.*;
/* Print a table of Fahrenheit and Celsius temperatures, with decimal
* points lined up.
*/
public class TempConverter3 extends TempConverter2 {
protected FieldPosition fp;
protected DecimalFormat dff;
public static void main(String[] args) {
TempConverter t = new TempConverter3( );
t.start( );
t.data( );
t.end( );
}
// Constructor
public TempConverter3( ) {
super( );
dff = new DecimalFormat("##.#");
fp = new FieldPosition(NumberFormat.INTEGER_FIELD);
}
protected void print(float f, float c) {
String fs = dff.format(f, new StringBuffer( ), fp).toString( );
fs = prependSpaces(4 - fp.getEndIndex( ), fs);
String cs = df.format(c, new StringBuffer( ), fp).toString( );
cs = prependSpaces(4 - fp.getEndIndex( ), cs);
System.out.println(fs + "
" + cs);
}
protected String prependSpaces(int n, String s) {
String[] res = {
"", " ", " ", "
", "
", "
"
};
if (n<res.length)
return res[n] + s;
throw new IllegalStateException("Rebuild with bigger \"res\" array.");
}
}

Java Handling Very Large Numbers

Java Handling Very Large Numbers


Problem

You need to handle integer numbers larger than Long.MAX_VALUE or floating-point val-
ues larger than Double.MAX_VALUE .

Solution

Use the BigInteger or BigDecimal values in package java.math :

// BigNums.java
System.out.println("Here's Long.MAX_VALUE: " + Long.MAX_VALUE);
BigInteger bInt = new BigInteger("3419229223372036854775807");
System.out.println("Here's a bigger number: " + bInt);
System.out.println("Here it is as a double: " + bInt.doubleValue( ));

Note that the constructor takes the number as a string. Obviously you couldn’t just type the numeric digits since by definition these classes are designed to represent numbers larger than will fit in a Java long .

Explained 

Both BigInteger and BigDecimal objects are immutable; that is, once constructed, they always represent a given number. That said, a number of methods return new objects that are mutations of the original, such as negate( ) , which returns the nega- tive of the given BigInteger or BigDecimal . There are also methods corresponding to most of the Java language built-in operators defined on the base types int / long and float / double . The division method makes you specify the rounding method; consult a book on numerical analysis for details. Example is a simple stack-based calcula- tor using BigDecimal as its numeric data type.

Example BigNumCalc
import java.math.BigDecimal;
import java.util.Stack;
/** A trivial reverse-polish stack-based calculator for big numbers */
public class BigNumCalc {
/** an array of Objects, simulating user input */
public static Object[] testInput = {
new BigDecimal("3419229223372036854775807.23343"),
new BigDecimal("2.0"),
"*",
};
public static void main(String[] args) {
BigNumCalc calc = new BigNumCalc( );
System.out.println(calc.calculate(testInput));
}

Stack s = new Stack( );
public BigDecimal calculate(Object[] input) {
BigDecimal tmp;
for (int i = 0; i < input.length; i++) {
Object o = input[i];
if (o instanceof BigDecimal)
s.push(o);
else if (o instanceof String) {
switch (((String)o).charAt(0)) {
// + and * are commutative, order doesn't matter
case '+':
s.push(((BigDecimal)s.pop()).add((BigDecimal)s.pop( )));
break;
case '*':
s.push(((BigDecimal)s.pop()).multiply((BigDecimal)s.pop( )));
break;
// - and /, order *does* matter
case '-':
tmp = (BigDecimal)s.pop( );
s.push(((BigDecimal)s.pop( )).subtract(tmp));
break;
case '/':
tmp = (BigDecimal)s.pop( );
s.push(((BigDecimal)s.pop( )).divide(tmp,
BigDecimal.ROUND_UP));
break;
default:
throw new IllegalStateException("Unknown OPERATOR popped");
}
} else {
throw new IllegalStateException("Syntax error in input");
}
}
return (BigDecimal)s.pop( );
}
}

Running this produces the expected (very large) value:

> jikes +E -d . BigNumCalc.java
> java BigNumCalc
6838458446744073709551614.466860
>

The current version has its inputs hard-coded, as does the JUnit test program, but in real life you can use regular expressions to extract words or operators from an input stream (as in Recipe 4.5), or you can use the StreamTokenizer approach of the simple calculator (Recipe 10.4). The stack of numbers is maintained using a java.util. Stack (Recipe 7.14). BigInteger is mainly useful in cryptographic and security applications. Its method isProbablyPrime( ) can create prime pairs for public key cryptography. BigDecimal might also be useful in computing the size of the universe.

Java Formatting with Correct Plurals

Java Formatting with Correct Plurals

Problem

You’re printing something like "We used " + n + " items" , but in English, “We used 1
items” is ungrammatical. You want “We used 1 item.”

Solution

Use a ChoiceFormat or a conditional statement.

Use Java’s ternary operator ( cond ? trueval : falseval ) in a string concatenation.
Both zero and plurals get an “s” appended to the noun in English, so we test for n==1 .

// FormatPlurals.java
public static void main(String argv[]) {
report(0);
report(1);
report(2);
}
/** report -- using conditional operator */
public static void report(int n) {
System.out.println("We used " + n + " item" + (n==1?"":"s"));
}

Does it work?

$ java FormatPlurals
We used 0 items
We used 1 item
We used 2 items
$

The final println statement is short for:

if (n==1)
System.out.println("We used " + n + " item");
else
System.out.println("We used " + n + " items");

This is a lot longer, in fact, so the ternary conditional operator is worth learning. The ChoiceFormat is ideal for this. It is actually capable of much more, but here I’ll show only this simplest use. I specify the values 0, 1, and 2 (or more), and the string values to print corresponding to each number. The numbers are then formatted according to the range they fall into:

import java.text.*;
/**
* Format a plural correctly, using a ChoiceFormat.
*/
public class FormatPluralsChoice extends FormatPlurals {
static double[] limits = { 0, 1, 2 };
static String[] formats = { "items", "item", "items"};
static ChoiceFormat myFormat = new ChoiceFormat(limits, formats);
/** report -- using conditional operator */
public static void report(int n) {
System.out.println("We used " + n + " " + myFormat.format(n));
}
public static void main(String[] argv) {
report(0);
report(1);
report(2);
}
}

This generates the same output as the basic version. It is slightly longer, but more general, and lends itself better to internationalization.

Java Working with Roman Numerals

Java Working with Roman Numerals

Problem

You need to format numbers as Roman numerals. Perhaps you’ve just written the
next Titanic or Star Wars episode and you need to get the copyright date correct. Or,
on a more mundane level, you need to format page numbers in the front matter of a
book.

Solution

Use my RomanNumberFormat class:

// RomanNumberSimple.java
RomanNumberFormat nf = new RomanNumberFormat( );
int year = Calendar.getInstance( ).get(Calendar.YEAR);
System.out.println(year + " -> " + nf.format(year));

The use of Calendar to get the current year is explained in Recipe 6.1. Running RomanNumberSimple looks like this:

+ jikes +E -d . RomanNumberSimple.java
+ java RomanNumberSimple
2004 -> MMIV

Explained

Nothing in the standard API formats Roman numerals. However, the java.text. Format class is designed to be subclassed for precisely such unanticipated purposes, so I have done just that and developed a class to format numbers as Roman numer- als. Here is a better and complete example program of using it to format the current year. I can pass a number of arguments on the command line, including a "-" where I want the year to appear (note that these arguments are normally not quoted; the "-" must be an argument all by itself, just to keep the program simple). I use it as follows:

$ java RomanYear
Copyright (c) - Ian Darwin
Copyright (c) MMIV Ian Darwin
$

The code for the RomanYear program is simple, yet it correctly puts spaces around the arguments:

import java.util.*;
/** Print the current year in Roman Numerals */
public class RomanYear {
public static void main(String[] argv) {
RomanNumberFormat rf = new RomanNumberFormat( );
Calendar cal = Calendar.getInstance( );
int year = cal.get(Calendar.YEAR);
// If no arguments, just print the year.
if (argv.length == 0) {
System.out.println(rf.format(year));
return;
}
// Else a micro-formatter: replace "-" arg with year, else print.
for (int i=0; i<argv.length; i++) {
if (argv[i].equals("-"))
System.out.print(rf.format(year));
else
System.out.print(argv[i]);
// e.g., "Copyright"
System.out.print(' ');
}
System.out.println( );
}
}

Now here’s the code for the RomanNumberFormat class. I did sneak in one additional class, java.text.FieldPosition . A FieldPosition simply represents the position of one numeric field in a string that has been formatted using a variant of NumberFormat. format( ) . You construct it to represent either the integer part or the fraction part (of course, Roman numerals don’t have fractional parts). The FieldPosition methods getBeginIndex( ) and getEndIndex( ) indicate where in the resulting string the given field wound up. Example is the class that implements Roman number formatting. As the com- ments indicate, the one limitation is that the input number must be less than 4,000.

Example RomanNumberFormat.java

import java.text.*;
import java.util.*;
/**
* Roman Number class. Not localized, since Latin's a Dead Dead Language
* and we don't display Roman Numbers differently in different Locales.
* Filled with quick-n-dirty algorithms.
*/
public class RomanNumberFormat extends Format {
/** Characters used in "Arabic to Roman", that is, format( ) methods. */
static char A2R[][] = {
{ 0, 'M' },
{ 0, 'C', 'D', 'M' },
{ 0, 'X', 'L', 'C' },
{ 0, 'I', 'V', 'X' },
};

/** Format a given double as a Roman Numeral; just truncate to a
* long, and call format(long).
*/
public String format(double n) {
return format((long)n);
}
/** Format a given long as a Roman Numeral. Just call the
* three-argument form.
*/
public String format(long n) {
if (n <= 0 || n >= 4000)
throw new IllegalArgumentException(n + " must be > 0 && < 4000");
StringBuffer sb = new StringBuffer( );
format(new Integer((int)n), sb, new FieldPosition(NumberFormat.INTEGER
FIELD));
return sb.toString( );
}

/* Format the given Number as a Roman Numeral, returning the
* Stringbuffer (updated), and updating the FieldPosition.
* This method is the REAL FORMATTING ENGINE.
* Method signature is overkill, but required as a subclass of Format.
*/
public StringBuffer format(Object on, StringBuffer sb, FieldPosition fp) {
if (!(on instanceof Number))
throw new IllegalArgumentException(on + " must be a Number object");

if (fp.getField( ) != NumberFormat.INTEGER_FIELD)
throw new IllegalArgumentException(fp +
" must be FieldPosition(NumberFormat.INTEGER_FIELD");
int n = ((Number)on).intValue( );
// TODO check for in range here
// First, put the digits on a tiny stack. Must be 4 digits.
for (int i=0; i<4; i++) {
int d=n%10;
push(d);
// System.out.println("Pushed " + d);
n=n/10;
}

// Now pop and convert.
for (int i=0; i<4; i++) {
int ch = pop( );
// System.out.println("Popped " + ch);
if (ch==0)
continue;
else if (ch <= 3) {
for(int k=1; k<=ch; k++)
sb.append(A2R[i][1]); // I
}
else if (ch == 4) {
sb.append(A2R[i][1]);
// I
sb.append(A2R[i][2]);
// V
}
else if (ch == 5) {
sb.append(A2R[i][2]);
// V
}
else if (ch <= 8) {
sb.append(A2R[i][2]);
// V
for (int k=6; k<=ch; k++)
sb.append(A2R[i][1]);
// I
}
else { // 9
sb.append(A2R[i][1]);
sb.append(A2R[i][3]);
}
}
// fp.setBeginIndex(0);
// fp.setEndIndex(3);
return sb;
}

/** Parse a generic object, returning an Object */
public Object parseObject(String what, ParsePosition where) {
throw new IllegalArgumentException("Parsing not implemented");
// TODO PARSING HERE
// return new Long(0);
}
/* Implement a toy stack */
protected int stack[] = new int[10];
protected int depth = 0;
/* Implement a toy stack */
protected void push(int n) {
stack[depth++] = n;
}
/* Implement a toy stack */
protected int pop( ) {
return stack[--depth];
}
}

Several of the public methods are required because I wanted it to be a subclass of Format , which is abstract. This accounts for some of the complexity, like having three different format methods. Note that the parseObject( ) method is also required, but we don’t actually implement parsing in this version. This is left as the usual exercise for the reader.

Java Rounding Floating-Point Numbers

Java Rounding Floating-Point Numbers

Problem

You need to round floating-point numbers to integers or to a particular precision.

Solution

If you simply cast a floating value to an integer value, Java truncates the value. A
value like 3.999999 cast to an int or long becomes 3, not 4. To round floating-point
numbers properly, use Math.round( ) . It has two forms: if you give it a double , you get
a long result; if you give it a float , you get an int .

What if you don’t like the rounding rules used by round ? If for some bizarre reason
you wanted to round numbers greater than 0.54 instead of the normal 0.5, you could
write your own version of round( ) :

/*
* Round floating values to integers.
* @Return the closest int to the argument.
* @param d A non-negative values to be rounded.
*/
static int round(double d) {
if (d < 0) {
throw new IllegalArgumentException("Value must be non-negative");
}
int di = (int)Math.floor(d);
// integral value below (or ==) d
if ((d - di) > THRESHOLD) {
return di + 1;
} else {
return di;
}
}

If you need to display a number with less precision than it normally gets, you proba- bly want to use a DecimalFormat object or a Formatter object.

Java Comparing Floating-Point Numbers

Java Comparing Floating-Point Numbers

Problem

You want to compare two floating-point numbers for equality.

Solution

Based on what we’ve just discussed, you probably won’t just go comparing two
floats or doubles for equality. You might expect the floating-point wrapper classes,

Float and Double , to override the equals( ) method, which they do. The equals( )
method returns true if the two values are the same bit for bit, that is, if and only if
the numbers are the same or are both NaN . It returns false otherwise, including if the
argument passed in is null, or if one object is +0.0 and the other is –0.0.

If this sounds weird, remember that the complexity comes partly from the nature of
doing real number computations in the less-precise floating-point hardware, and
partly from the details of the IEEE Standard 754, which specifies the floating-point
functionality that Java tries to adhere to, so that underlying floating-point processor
hardware can be used even when Java programs are being interpreted.

To actually compare floating-point numbers for equality, it is generally desirable to
compare them within some tiny range of allowable differences; this range is often
regarded as a tolerance or as epsilon. Example shows an equals( ) method you
can use to do this comparison, as well as comparisons on values of NaN . When run, it
prints that the first two numbers are equal within epsilon:

$ java FloatCmp
True within epsilon 1.0E-7
$

Example. FloatCmp.java
}/**
* Floating-point comparisons.
*/
public class FloatCmp {
final static double EPSILON = 0.0000001;
public static void main(String[] argv) {
double da = 3 * .3333333333;
double db = 0.99999992857;
// Compare two numbers that are expected to be close.
if (da == db) {
System.out.println("Java considers " + da + "==" + db);
// else compare with our own equals method
} else if (equals(da, db, 0.0000001)) {
System.out.println("True within epsilon " + EPSILON);
} else {
System.out.println(da + " != " + db);
}
// Show that comparing two NaNs is not a good idea:
double d1 = Double.NaN;
double d2 = Double.NaN;
if (d1 == d2)
System.err.println("Comparing two NaNs incorrectly returns true.");
if (!new Double(d1).equals(new Double(d2)))
System.err.println("Double(NaN).equal(NaN) incorrectly returns false.");
}

/** Compare two doubles within a given epsilon */
public static boolean equals(double a, double b, double eps) {
if (a==b) return true;
// If the difference is less than epsilon, treat as equal.
return Math.abs(a - b) < eps;
}
/** Compare two doubles, using default epsilon */
public static boolean equals(double a, double b) {
if (a==b) return true;
// If the difference is less than epsilon, treat as equal.
return Math.abs(a - b) < EPSILON * Math.max(Math.abs(a), Math.abs(b));
}
}

Note that neither of the System.err messages about “incorrect returns” prints. The point of this example with NaN s is that you should always make sure values are not NaN before entrusting them to Double.equals( ).

Java Ensuring the Accuracy of Floating-Point Numbers

Java Ensuring the Accuracy of Floating-Point
Numbers

Problem

You want to know if a floating-point computation generated a sensible result.

Solution

Compare with the INFINITY constants, and use isNaN( ) to check for “not a number.”
Fixed-point operations that can do things like divide by zero result in Java notifying
you abruptly by throwing an exception. This is because integer division by zero is
considered a logic error.

Floating-point operations, however, do not throw an exception because they are
defined over an (almost) infinite range of values. Instead, they signal errors by pro-
ducing the constant POSITIVE_INFINITY if you divide a positive floating-point num-
ber by zero, the constant NEGATIVE_INFINITY if you divide a negative floating-point
value by zero, and NaN (Not a Number), if you otherwise generate an invalid result.
Values for these three public constants are defined in both the Float and the Double wrapper classes.

The value NaN has the unusual property that it is not equal to itself,
that is, NaN != NaN . Thus, it would hardly make sense to compare a (possibly sus-
pect) number against NaN , because the expression:

x == NaN

can never be true. Instead, the methods Float.isNaN(float) and Double. isNaN(double) must be used:

// InfNaN.java
public static void main(String argv[]) {
double d = 123;
double e = 0;
if (d/e == Double.POSITIVE_INFINITY)
System.out.println("Check for POSITIVE_INFINITY works");
double s = Math.sqrt(-1);
if (s == Double.NaN)
System.out.println("Comparison with NaN incorrectly returns true");
if (Double.isNaN(s))
System.out.println("Double.isNaN( ) correctly returns true");
}

Note that this, by itself, is not sufficient to ensure that floating-point calculations have been done with adequate accuracy. For example, the following program dem- onstrates a contrived calculation—Heron’s formula for the area of a triangle—both in float and in double . The double values are correct, but the floating-point value comes out as zero due to rounding errors. This happens because, in Java, operations involving only float values are performed as 32-bit calculations. Related languages such as C automatically promote these to double during the computation, which can eliminate some loss of accuracy.

/** Compute the area of a triangle using Heron's Formula.
* Code and values from Prof W. Kahan and Joseph D. Darcy.
* See http://www.cs.berkeley.edu/~wkahan/JAVAhurt.pdf.
* Derived from listing in Rick Grehan's Java Pro article (October 1999).
* Simplified and reformatted by Ian Darwin.
*/
public class Heron {
public static void main(String[] args) {
// Sides for triangle in float
float af, bf, cf;
float sf, areaf;
// Ditto in double
double ad, bd, cd;
double sd, aread;
af
bf
cf
//Area of triangle in float
= 12345679.0f;
= 12345678.0f;
= 1.01233995f;
sf = (af+bf+cf)/2.0f;
areaf = (float)Math.sqrt(sf * (sf - af) * (sf - bf) * (sf - cf));
System.out.println("Single precision: " + areaf);
//
ad
bd
cd
Area of triangle in double
= 12345679.0;
= 12345678.0;
= 1.01233995;
sd = (ad+bd+cd)/2.0d;
aread =
Math.sqrt(sd * (sd - ad) * (sd - bd) * (sd - cd));
System.out.println("Double precision: " + aread);
}
}

Let’s run it. To ensure that the rounding is not an implementation artifact, I’ll try it both with Sun’s JDK and with Kaffe:

$ java Heron
Single precision:
Double precision:
$ kaffe Heron
Single precision:
Double precision:
0.0
972730.0557076167
0.0
972730.05570761673

If in doubt, use double ! To ensure consistency of very large magnitude double computations on different Java implementations, Java provides the keyword strictfp , which can apply to classes, interfaces, or methods within a class. * If a computation is Strict-FP, then it must always, for example, return the value INFINITY if a calculation would overflow the value of Double.MAX_VALUE (or underflow the value Double.MIN_VALUE ). Non-Strict- FP calculations—the default—are allowed to perform calculations on a greater range and can return a valid final result that is in range even if the interim product was out of range. This is pretty esoteric and affects only computations that approach the bounds of what fits into a double.

Java Taking a Fraction of an Integer Without Using Floating Point

Java Taking a Fraction of an Integer Without
Using Floating Point

Problem

You want to multiply an integer by a fraction without converting the fraction to a
floating-point number.

Solution

Multiply the integer by the numerator and divide by the denominator.
This technique should be used only when efficiency is more important than clarity,
as it tends to detract from the readability—and therefore the maintainability—of
your code.

Explained

Since integers and floating-point numbers are stored differently, it may sometimes be
desirable and feasible, for efficiency purposes, to multiply an integer by a fractional
value without converting the values to floating point and back, and without requiring a “cast”:

/** Compute the value of 2/3 of 5 */
public class FractMult {
public static void main(String u[]) {
double d1 = 0.666 * 5;
// fast but obscure and inaccurate: convert
System.out.println(d1); // 2/3 to 0.666 in programmer's head
double d2 = 2/3 * 5;
// wrong answer - 2/3 == 0, 0*5 = 0
System.out.println(d2);
double d3 = 2d/3d * 5;
System.out.println(d3);
double d4 = (2*5)/3d;
System.out.println(d4);
int i5 = 2*5/3;
System.out.println(i5);
// "normal"
// one step done as integers, almost same answer
// fast, approximate integer answer
}
}

Running it looks like this:
$ java FractMult
3.33
0.0
3.333333333333333
3.3333333333333335
3
$

Java Converting Numbers to Objects and Vice Versa

Java Converting Numbers to Objects
and Vice Versa

Problem

You need to convert numbers to objects and objects to numbers.

Solution

Use the Object Wrapper classes listed in Table at the beginning.

Explained

Often you have a primitive number and you need to pass it into a method where an
Object is required. This frequently happens when using the Collection classes and earlier.

To convert between an int and an Integer object, or vice versa, you can use the
following:

// IntObject.java
// int to Integer
Integer i1 = new Integer(42);
System.out.println(i1.toString( )); // or just i1
// Integer to int
int i2 = i1.intValue( );
System.out.println(i2);

Java Storing a Larger Number in a Smaller Number

Java Storing a Larger Number
in a Smaller Number

Problem

You have a number of a larger type and you want to store it in a variable of a smaller
type.

Solution

Cast the number to the smaller type. (A cast is a type listed in parentheses before a
value that causes the value to be treated as though it were of the listed type.)
For example, to cast a long to an int , you need a cast. To cast a double to a float ,
you also need a cast.

Explained

This causes newcomers some grief, as the default type for a number with a decimal
point is double , not float . So code like:

float f = 3.0;

won’t even compile! It’s as if you had written:

double tmp = 3.0;
float f = tmp;

You can fix it by making f a double , by making the 3.0 a float , by putting in a cast, or by assigning an integer value of 3:

double f = 3.0;
float f = 3.0f;
float f = 3f;
float f = (float)3.0;
float f = 3;

The same applies when storing an int into a short , char , or byte :

// CastNeeded.java
public static void main(String argv[]) {
int i;
double j = 2.75;
i = j;
// EXPECT COMPILE ERROR
i = (int)j;
// with cast; i gets 2
System.out.println("i =" + i);
byte b;
b = i;
// EXPECT COMPILE ERROR
b = (byte)i;
// with cast, i gets 2
System.out.println("b =" + b);
}

The lines marked EXPECT COMPILE ERROR do not compile unless either com- mented out or changed to be correct. The lines marked “with cast” show the correct forms.

Java Finding the Matching Text

Java Finding the Matching Text


Problem

You need to find the text that the regex matched.

Solution

Sometimes you need to know more than just whether a regex matched a string. In
editors and many other tools, you want to know exactly what characters were
matched. Remember that with multipliers such as * , the length of the text that was
matched may have no relationship to the length of the pattern that matched it. Do
not underestimate the mighty .* , which happily matches thousands or millions of
characters if allowed to. As you saw in the previous recipe, you can find out whether
a given match succeeds just by using find( ) or matches( ) . But in other applications,
you will want to get the characters that the pattern matched.

After a successful call to one of the above methods, you can use these “information”
methods to get information on the match:

start(), end( )
Returns the character position in the string of the starting and ending characters
that matched.

groupCount( )
Returns the number of parenthesized capture groups if any; returns 0 if no
groups were used.

group(int i)
Returns the characters matched by group i of the current match, if i is less than
or equal to the return value of groupCount( ) . Group 0 is the entire match, so
group(0) (or just group( ) ) returns the entire portion of the string that matched.

The notion of parentheses or “capture groups” is central to regex processing. Regexes
may be nested to any level of complexity. The group(int) method lets you retrieve
the characters that matched a given parenthesis group. If you haven’t used any
explicit parens, you can just treat whatever matched as “level zero.” For example:

// Part of REmatch.java
String patt = "Q[^u]\\d+\\.";
Pattern r = Pattern.compile(patt);
String line = "Order QT300. Now!";
Matcher m = r.matcher(line);
if (m.find( )) {
System.out.println(patt + " matches \"" +
m.group(0) +
"\" in \"" + line + "\"");
} else {
System.out.println("NO MATCH");
}

When run, this prints:

Q[^u]\d+\. matches "QT300." in "Order QT300. Now!"

It is also possible to get the starting and ending indexes and the length of the text that the pattern matched (remember that terms with multipliers, such as the \d+ in this example, can match an arbitrary number of characters in the string). You can use these in conjunction with the String.substring( ) methods as follows:

// Part of regexsubstr.java -- Prints exactly the same as REmatch.java
Pattern r = Pattern.compile(patt);
String line = "Order QT300. Now!";
Matcher m = r.matcher(line);
if (m.find( )) {
System.out.println(patt + " matches \"" +
line.substring(m.start(0), m.end(0)) +
"\" in \"" + line + "\"");
} else {
System.out.println("NO MATCH");
}
}

Suppose you need to extract several items from a string. If the input is:

Smith, John
Adams, John Quincy

and you want to get out:

John Smith
John Quincy Adams

just use:

// from REmatchTwoFields.java
// Construct a regex with parens to "grab" both field1 and field2
Pattern r = Pattern.compile("(.*), (.*)");
Matcher m = r.matcher(inputLine);
if (!m.matches( ))
throw new IllegalArgumentException("Bad input: " + inputLine);
System.out.println(m.group(2) + ' ' + m.group(1));

Java Program: A Simple Text Formatter

 Java Program: A Simple Text Formatter


This program is a very primitive text formatter, representative of what people used on most computing platforms before the rise of standalone graphics-based word processors, laser printers, and, eventually, desktop publishing, word processors, and desktop office suites. It simply reads words from a file—previously created with a text editor—and outputs them until it reaches the right margin, when it calls println( ) to append a line ending. For example, here is an input file:

It's a nice
day, isn't it, Mr. Mxyzzptllxy?
I think we should
go for a walk.

Given the above as its input, the Fmt program prints the lines formatted neatly:

It's a nice day, isn't it, Mr. Mxyzzptllxy? I think we should go for a
walk.

As you can see, it fits the text we gave it to the margin and discards all the line breaks present in the original. Here’s the code:

import java.io.*;
import java.util.*;
/**
 * Fmt - format text (like Berkeley Unix fmt).
 */
public class Fmt {
 /** The maximum column width */
 public static final int COLWIDTH=72;
 /** The file that we read and format */
 BufferedReader in;
 /** If files present, format each, else format the standard input. */
 public static void main(String[] av) throws IOException {
 if (av.length == 0)
 new Fmt(System.in).format( );
 else for (int i=0; i<av.length; i++)
 new Fmt(av[i]).format( );
 }
 /** Construct a Formatter given a filename */
 public Fmt(String fname) throws IOException {
 in = new BufferedReader(new FileReader(fname));
 }
 /** Construct a Formatter given an open Stream */
 public Fmt(InputStream file) throws IOException {
 in = new BufferedReader(new InputStreamReader(file));
 }
 /** Format the File contained in a constructed Fmt object */
 public void format( ) throws IOException {
 String w, f;
 int col = 0;
 while ((w = in.readLine( )) != null) {
 if (w.length( ) == 0) { // null line
 System.out.print("\n"); // end current line
 if (col>0) {
 System.out.print("\n"); // output blank line
 col = 0;
 }
 continue;
 }
// otherwise it's text, so format it.
 StringTokenizer st = new StringTokenizer(w);
 while (st.hasMoreTokens( )) {
 f = st.nextToken( );
 if (col + f.length( ) > COLWIDTH) {
 System.out.print("\n");
 col = 0;
 }
 System.out.print(f + " ");
 col += f.length( ) + 1;
 }
 }
 if (col>0) System.out.print("\n");
 in.close( );
 }
}

A slightly fancier version of this program, Fmt2, is in the online source for this book. It uses “dot commands”—lines beginning with periods—to give limited control over the formatting. A family of “dot command” formatters includes Unix’s roff, nroff, troff, and groff, which are in the same family with programs called runoff on Digital Equipment systems. The original for this is J. Saltzer’s runoff, which first appeared on Multics and from there made its way into various OSes. To save trees, I did not include Fmt2 here; it subclasses Fmt and overrides the format( ) method to include additional functionality.

Java Parsing Comma-Separated Data

 Java Parsing Comma-Separated Data

Problem 

You have a string or a file of lines containing comma-separated values (CSV) that you need to read. Many Windows-based spreadsheets and some databases use CSV to export data. 

Solution 

Use my CSV class or a regular expression. 

Explained

CSV is deceptive. It looks simple at first glance, but the values may be quoted or unquoted. If quoted, they may further contain escaped quotes. This far exceeds the capabilities of the StringTokenizer class (Recipe 3.2). Either considerable Java coding or the use of regular expressions is required. I’ll show both ways. First, a Java program. Assume for now that we have a class called CSV that has a noargument constructor and a method called parse( ) that takes a string representing one line of the input file. The parse( ) method returns a list of fields. For flexibility, the fields are returned as a List, from which you can obtain an Iterator. I simply use the Iterator’s hasNext() method to control the loop and its next( ) method to get the next object:



import java.util.*;
/* Simple demo of CSV parser class.
 */
public class CSVSimple {
public static void main(String[] args) {
 CSV parser = new CSV( );
 List list = parser.parse(
 "\"LU\",86.25,\"11/4/1998\",\"2:19PM\",+4.0625");
 Iterator it = list.iterator( );
 while (it.hasNext( )) {
 System.out.println(it.next( ));
 }
 }
}

After the quotes are escaped, the string being parsed is actually the following:

"LU",86.25,"11/4/1998","2:19PM",+4.0625

Running CSVSimple yields the following output:

> java CSVSimple
LU
86.25
11/4/1998
2:19PM
+4.0625
>

But what about the CSV class itself? The code in Example 3-10 started as a translation of a CSV program written in C++ by Brian W. Kernighan and Rob Pike that appeared in their book The Practice of Programming (Addison Wesley). Their version commingled the input processing with the parsing; my CSV class does only the parsing since the input could be coming from any of a variety of sources. And it has been substantially rewritten over time. The main work is done in parse( ), which delegates handling of individual fields to advquoted( ) in cases where the field begins with a quote; otherwise, to advplain( ).

CSV.java
import java.util.*;
import com.darwinsys.util.Debug;
/** Parse comma-separated values (CSV), a common Windows file format.
 * Sample input: "LU",86.25,"11/4/1998","2:19PM",+4.0625
 * * Inner logic adapted from a C++ original that was
 * Copyright (C) 1999 Lucent Technologies
 * Excerpted from 'The Practice of Programming'
 * by Brian W. Kernighan and Rob Pike.
 * 
* Included by permission of the http://tpop.awl.com/ web site, * which says: * "You may use this code for any purpose, as long as you leave * the copyright notice and book citation attached." I have done so. * @author Brian W. Kernighan and Rob Pike (C++ original) * @author Ian F. Darwin (translation into Java and removal of I/O) * @author Ben Ballard (rewrote advQuoted to handle '""' and for readability) */ public class CSV { public static final char DEFAULT_SEP = ','; /** Construct a CSV parser, with the default separator (','). */ public CSV( ) { this(DEFAULT_SEP); } /** Construct a CSV parser with a given separator. * @param sep The single char for the separator (not a list of * separator characters) */ public CSV(char sep) { fieldSep = sep; } /** The fields in the current String */ protected List list = new ArrayList( ); /** the separator char for this parser */ protected char fieldSep; /** parse: break the input String into fields * @return java.util.Iterator containing each field * from the original as a String, in order. */ public List parse(String line) { StringBuffer sb = new StringBuffer( ); list.clear( ); // recycle to initial state int i = 0; if (line.length( ) == 0) { list.add(line); return list; } do { sb.setLength(0); if (i < line.length( ) && line.charAt(i) == '"') i = advQuoted(line, sb, ++i); // skip quote else i = advPlain(line, sb, i); list.add(sb.toString( )); Debug.println("csv", sb.toString( )); i++; } while (i < line.length( )); return list; } /** advQuoted: quoted field; return index of next separator */ protected int advQuoted(String s, StringBuffer sb, int i) { int j; int len= s.length( ); for (j=i; j import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /* Simple demo of CSV matching using Regular Expressions. * Does NOT use the "CSV" class defined in the Java CookBook, but uses * a regex pattern simplified from Chapter 7 of Mastering Regular * Expressions (p. 205, first edn.) * @version $Id: ch03,v 1.3 2004/05/04 18:03:14 ian Exp $ */ public class CSVRE { /** The rather involved pattern used to match CSV's consists of three * alternations: the first matches aquoted field, the second unquoted, * the third a null field. */ public static final String CSV_PATTERN = "\"([^\"]+?)\",?|([^,]+),?|,"; private static Pattern csvRE; public static void main(String[] argv) throws IOException { System.out.println(CSV_PATTERN); new CSVRE().process(new BufferedReader(new InputStreamReader(System.in))); } /** Construct a regex-based CSV parser. */ public CSVRE() { csvRE = Pattern.compile(CSV_PATTERN); } /** Process one file. Delegates to parse() a line at a time */ public void process(BufferedReader in) throws IOException { String line; // For each line... while ((line = in.readLine()) != null) { System.out.println("line = `" + line + "'"); List l = parse(line); System.out.println("Found " + l.size() + " items."); for (int i = 0; i < l.size(); i++) { System.out.print(l.get(i) + ","); } System.out.println(); } } /** Parse one line. * @return List of Strings, minus their double quotes */ public List parse(String line) { List list = new ArrayList(); Matcher m = csvRE.matcher(line); // For each field while (m.find()) { System.out.println(m.groupCount()); String match = m.group(); if (match == null) break; if (match.endsWith(",")) {// trim trailing , match = match.substring(0, match.length() - 1); } if (match.startsWith("\"")) { // assume also ends with match = match.substring(1, match.length() - 1); } if (match.length() == 0) match = null; list.add(match); } return list; } }

It is sometimes “downright scary” how much mundane code you can eliminate with a single, well-formulated regular expression.

Java Trimming Blanks from the End of a String

 Java Trimming Blanks from the End of a String

Problem 

You need to work on a string without regard for extra leading or trailing spaces a user may have typed. 

Solution 

Use the String class trim() method. 

Explained

Uses trim( ) to strip an arbitrary number of leading spaces and/or tabs from lines of Java source code in order to look for the characters //+ and //-. These strings are special Java comments I use to mark the parts of the programs in this book that I want to include in the printed copy.


GetMark.java (trimming and comparing strings)
/** the default starting mark. */
public final String startMark = "//+";
/** the default ending mark. */
public final String endMark = "//-";
/** True if we are currently inside marks. */
protected boolean printing = false;
try {
 String inputLine;
 while ((inputLine = is.readLine( )) != null) {
 if (inputLine.trim( ).equals(startMark)) {
 printing = true;
 } else if (inputLine.trim( ).equals(endMark)) {
 printing = false;
 } else if (printing)
 System.out.println(inputLine);
 }
 is.close( );
 } catch (IOException e) {
 // not shown
 }
 }

Java Controlling Case

Java Controlling Case

Problem 

You need to convert strings to uppercase or lowercase, or to compare strings without regard for case. 

Solution 

The String class has a number of methods for dealing with documents in a particular case. toUpperCase( ) and toLowerCase( ) each return a new string that is a copy of the current string, but converted as the name implies. Each can be called either with no arguments or with a Locale argument specifying the conversion rules; this is necessary because of internationalization.

Java provides significantly more internationalization and localization features than ordinary languages, a feature that is covered in Chapter 15. While the equals( ) method tells you if another string is exactly the same, equalsIgnoreCase( ) tells you if all characters are the same regardless of case. Here, you can’t specify an alternate locale; the system’s default locale is used:

// Case.java
String name = "Java Cookbook";
System.out.println("Normal:\t" + name);
System.out.println("Upper:\t" + name.toUpperCase( ));
System.out.println("Lower:\t" + name.toLowerCase( ));
String javaName = "java cookBook"; // As if it were Java identifiers :-)
if (!name.equals(javaName))
 System.err.println("equals( ) correctly reports false");
else
 System.err.println("equals( ) incorrectly reports true");
if (name.equalsIgnoreCase(javaName))
 System.err.println("equalsIgnoreCase( ) correctly reports true");
else
 System.err.println("equalsIgnoreCase( ) incorrectly reports false");

If you run this, it prints the first name changed to uppercase and lowercase, then it reports that both methods work as expected.

C:\javasrc\strings>java Case
Normal: Java Cookbook
Upper: JAVA COOKBOOK
Lower: java cookbook
equals( ) correctly reports false
equalsIgnoreCase( ) correctly reports true

Java Expanding and Compressing Tabs

Java Expanding and Compressing Tabs

Problem 

You need to convert space characters to tab characters in a file, or vice versa. You might want to replace spaces with tabs to save space on disk, or go the other way to deal with a device or program that can’t handle tabs. 

Solution 

Use my Tabs class or its subclass EnTab. 

Explained 

EnTab, complete with a sample main program. The program works a line at a time. For each character on the line, if the character is a space, we see if we can coalesce it with previous spaces to output a single tab character. This program depends on the Tabs class, which we’ll come to shortly. The Tabs class is used to decide which column positions represent tab stops and which do not. The code also has several Debug printouts.

Entab.java
/**
 * EnTab: replace blanks by tabs and blanks. Transmuted from K&R Software Tools
 * book into C. Transmuted again, years later, into Java. Totally rewritten to
 * be line-at-a-time instead of char-at-a-time.
 *
 * @author Ian F. Darwin, http://www.darwinsys.com/
 * @version $Id: ch03,v 1.3 2004/05/04 18:03:14 ian Exp $
 */
public class EnTab {
 /** The Tabs (tab logic handler) */
 protected Tabs tabs;
 /**
 * Delegate tab spacing information to tabs.
 *
 * @return
 */
 public int getTabSpacing( ) {
 return tabs.getTabSpacing( );
 }
 /**
 * Main program: just create an EnTab object, and pass the standard input
 * or the named file(s) through it.
 */
 public static void main(String[] argv) throws IOException {
 EnTab et = new EnTab(8);
 if (argv.length == 0) // do standard input
 et.entab(
 new BufferedReader(new InputStreamReader(System.in)),
 System.out);
 else
 for (int i = 0; i < argv.length; i++) { // do each file
 et.entab(
 new BufferedReader(new FileReader(argv[i])),
 System.out);
 }
 }
 /**
 * Constructor: just save the tab values.
 *
 * @param n
 * The number of spaces each tab is to replace.
 */
 public EnTab(int n) {
 tabs = new Tabs(n);
 }
 public EnTab( ) {
 tabs = new Tabs( );
 }
/**
 * entab: process one file, replacing blanks with tabs.
 *
 * @param is A BufferedReader opened to the file to be read.
 * @param out a PrintWriter to send the output to.
 */
 public void entab(BufferedReader is, PrintWriter out) throws IOException {
 String line;
 int c, col = 0, newcol;
 // main loop: process entire file one line at a time.
 while ((line = is.readLine( )) != null) {
 out.println(entabLine(line));
 }
 }
 /**
 * entab: process one file, replacing blanks with tabs.
 *
 * @param is A BufferedReader opened to the file to be read.
 * @param out A PrintStream to write the output to.
 */
 public void entab(BufferedReader is, PrintStream out) throws IOException {
 entab(is, new PrintWriter(out));
 }
 /**
 * entabLine: process one line, replacing blanks with tabs.
 *
 * @param line -
 * the string to be processed
 */
 public String entabLine(String line) {
 int N = line.length( ), outCol = 0;
 StringBuffer sb = new StringBuffer( );
 char ch;
 int consumedSpaces = 0;
 for (int inCol = 0; inCol < N; inCol++) {
 ch = line.charAt(inCol);
 // If we get a space, consume it, don't output it.
 // If this takes us to a tab stop, output a tab character.
 if (ch == ' ') {
 Debug.println("space", "Got space at " + inCol);
 if (!tabs.isTabStop(inCol)) {
 consumedSpaces++;
 } else {
 Debug.println("tab", "Got a Tab Stop "+ inCol);
 sb.append('\t');
 outCol += consumedSpaces;
 consumedSpaces = 0;
 }
 continue;
 }
// We're at a non-space; if we're just past a tab stop, we need
 // to put the "leftover" spaces back out, since we consumed
 // them above.
 while (inCol-1 > outCol) {
 Debug.println("pad", "Padding space at "+ inCol);
 sb.append(' ');
 outCol++;
 }
 // Now we have a plain character to output.
 sb.append(ch);
 outCol++;
 }
 // If line ended with trailing (or only!) spaces, preserve them.
 for (int i = 0; i < consumedSpaces; i++) {
 Debug.println("trail", "Padding space at end # " + i);
 sb.append(' ');
 }
 return sb.toString( );
 }
}

As the comments state, this code was patterned after a program in Kernighan and Plauger’s classic work, Software Tools. While their version was in a language called RatFor (Rational Fortran), my version has since been through several translations. Their version actually worked one character at a time, and for a long time I tried to preserve this overall structure. For this edition of the book, I finally rewrote it to be a line-at-a-time program. The program that goes in the opposite direction—putting tabs in rather than taking them out—is the DeTab class; only the core methods are shown.

 DeTab.java
public class DeTab {
 Tabs ts; // iniitialized in Constructor
 public static void main(String[] argv) throws IOException {
 DeTab dt = new DeTab(8);
 dt.detab(new BufferedReader(new InputStreamReader(System.in)),
 new PrintWriter(System.out));
 }
 /** detab one file (replace tabs with spaces)
 * @param is - the file to be processed
 * @param out - the updated file
 */
 public void detab(BufferedReader is, PrintWriter out) throws IOException {
 String line;
char c;
 int col;
 while ((line = is.readLine( )) != null) {
 out.println(detabLine(line));
 }
 }
 /** detab one line (replace tabs with spaces)
 * @param line - the line to be processed
 * @return the updated line
 */
 public String detabLine(String line) {
 char c;
 int col;
 StringBuffer sb = new StringBuffer( );
 col = 0;
 for (int i = 0; i < line.length( ); i++) {
 // Either ordinary character or tab.
 if ((c = line.charAt(i)) != '\t') {
 sb.append(c); // Ordinary
 ++col;
 continue;
 }
 do { // Tab, expand it, must put >=1 space
 sb.append(' ');
 } while (!ts.isTabStop(++col));
 }
 return sb.toString( );
 }
}

The Tabs class provides two methods, settabpos( ) and istabstop( ). Example 3-8 lists the source for the Tabs class.

Tabs.java
public class Tabs {
 /** tabs every so often */
 public final static int DEFTABSPACE = 8;
 /** the current tab stop setting. */
 protected int tabSpace = DEFTABSPACE;
 /** The longest line that we worry about tabs for. */
 public final static int MAXLINE = 250;
 /** the current tab stops */
 protected boolean[] tabstops;
 /** Construct a Tabs object with a given tab stop settings */
 public Tabs(int n) {
 if (n <= 0)
 n = 1;
 tabstops = new boolean[MAXLINE];
 tabSpace = n;
settabs( );
 }
 /** Construct a Tabs object with a default tab stop settings */
 public Tabs( ) {
 this(DEFTABSPACE);
 }
 /** settabs - set initial tab stops */
 private void settabs( ) {
 for (int i = 0; i < tabstops.length; i++) {
 tabstops[i] = ((i+1) % tabSpace) == 0;
 Debug.println("tabs", "Tabs[" + i + "]=" + tabstops[i]);
 }
 }
 /**
 * @return Returns the tabSpace.
 */
 public int getTabSpacing( ) {
 return tabSpace;
 }
 /** isTabStop - returns true if given column is a tab stop.
 * If current input line is too long, we just put tabs wherever,
 * no exception is thrown.
 * @param col - the current column number
 */
 public boolean isTabStop(int col) {
 if (col > tabstops.length-1) {
 tabstops = new boolean[tabstops.length * 2;
 settabs( );
 }
 return tabstops[col];
 }
}