blob: 24ac3d067dc3dc24011f00d9bbed9d4bf60b52cb [file] [log] [blame]
/*
* Copyright 2003-2007 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
/* This file has been extensively modified from the original Sun implementation
* to provide for compile time checking of Format Strings.
*
* These modifications were performed by Bill Pugh, this code is free software.
*
*/
package edu.umd.cs.findbugs.formatStringChecker;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Calendar;
import java.util.Date;
import java.util.FormatFlagsConversionMismatchException;
import java.util.GregorianCalendar;
import java.util.IllegalFormatFlagsException;
import java.util.IllegalFormatPrecisionException;
import java.util.IllegalFormatWidthException;
import java.util.MissingFormatWidthException;
import java.util.UnknownFormatConversionException;
public class FormatSpecifier {
private int index = -1;
private Flags f = Flags.NONE;
private int width;
private int precision;
private boolean dt = false;
private char c;
private final String source;
public String toString() {
return source;
}
private int index(String s) throws FormatterNumberFormatException {
if (s != null) {
try {
index = Integer.parseInt(s.substring(0, s.length() - 1));
} catch (NumberFormatException x) {
throw new FormatterNumberFormatException(s, "index");
}
} else {
index = 0;
}
return index;
}
public int index() {
return index;
}
private Flags flags(String s) {
f = Flags.parse(s);
if (f.contains(Flags.PREVIOUS))
index = -1;
return f;
}
Flags flags() {
return f;
}
private int width(String s) throws FormatterNumberFormatException {
width = -1;
if (s != null) {
try {
width = Integer.parseInt(s);
if (width < 0)
throw new IllegalFormatWidthException(width);
} catch (NumberFormatException x) {
throw new FormatterNumberFormatException(s, "width");
}
}
return width;
}
private int precision(String s) throws FormatterNumberFormatException {
precision = -1;
if (s != null) {
try {
// remove the '.'
precision = Integer.parseInt(s.substring(1));
if (precision < 0)
throw new IllegalFormatPrecisionException(precision);
} catch (NumberFormatException x) {
throw new FormatterNumberFormatException(s, "precision");
}
}
return precision;
}
int precision() {
return precision;
}
private char conversion(String s) {
c = s.charAt(0);
if (!dt) {
if (!Conversion.isValid(c))
throw new UnknownFormatConversionException(String.valueOf(c));
if (Character.isUpperCase(c))
f.add(Flags.UPPERCASE);
c = Character.toLowerCase(c);
if (Conversion.isText(c))
index = -2;
}
return c;
}
FormatSpecifier(String source, String[] sa)
throws FormatFlagsConversionMismatchException,
FormatterNumberFormatException {
int idx = 0;
this.source = source;
index(sa[idx++]);
flags(sa[idx++]);
width(sa[idx++]);
precision(sa[idx++]);
if (sa[idx] != null) {
dt = true;
if (sa[idx].equals("T"))
f.add(Flags.UPPERCASE);
}
conversion(sa[++idx]);
if (dt)
checkDateTime();
else if (Conversion.isGeneral(c))
checkGeneral();
else if (Conversion.isCharacter(c))
checkCharacter();
else if (Conversion.isInteger(c))
checkInteger();
else if (Conversion.isFloat(c))
checkFloat();
else if (Conversion.isText(c))
checkText();
else
throw new UnknownFormatConversionException(String.valueOf(c));
}
private void checkGeneral() throws FormatFlagsConversionMismatchException {
if ((c == Conversion.BOOLEAN || c == Conversion.HASHCODE)
&& f.contains(Flags.ALTERNATE))
failMismatch(Flags.ALTERNATE, c);
// '-' requires a width
if (width == -1 && f.contains(Flags.LEFT_JUSTIFY))
throw new MissingFormatWidthException(toString());
checkBadFlags(Flags.PLUS, Flags.LEADING_SPACE, Flags.ZERO_PAD,
Flags.GROUP, Flags.PARENTHESES);
}
private void checkDateTime() throws FormatFlagsConversionMismatchException {
if (precision != -1)
throw new IllegalFormatPrecisionException(precision);
if (!DateTime.isValid(c))
throw new UnknownFormatConversionException("t" + c);
checkBadFlags(Flags.ALTERNATE, Flags.PLUS, Flags.LEADING_SPACE,
Flags.ZERO_PAD, Flags.GROUP, Flags.PARENTHESES);
// '-' requires a width
if (width == -1 && f.contains(Flags.LEFT_JUSTIFY))
throw new MissingFormatWidthException(toString());
}
private void checkCharacter() throws FormatFlagsConversionMismatchException {
if (precision != -1)
throw new IllegalFormatPrecisionException(precision);
checkBadFlags(Flags.ALTERNATE, Flags.PLUS, Flags.LEADING_SPACE,
Flags.ZERO_PAD, Flags.GROUP, Flags.PARENTHESES);
// '-' requires a width
if (width == -1 && f.contains(Flags.LEFT_JUSTIFY))
throw new MissingFormatWidthException(toString());
}
private void checkInteger() throws FormatFlagsConversionMismatchException {
checkNumeric();
if (precision != -1)
throw new IllegalFormatPrecisionException(precision);
if (c == Conversion.DECIMAL_INTEGER)
checkBadFlags(Flags.ALTERNATE);
else
checkBadFlags(Flags.GROUP);
}
private void checkBadFlags(Flags... badFlags)
throws FormatFlagsConversionMismatchException {
for (int i = 0; i < badFlags.length; i++)
if (f.contains(badFlags[i]))
failMismatch(badFlags[i], c);
}
private void checkFloat() throws FormatFlagsConversionMismatchException {
checkNumeric();
if (c == Conversion.DECIMAL_FLOAT) {
} else if (c == Conversion.HEXADECIMAL_FLOAT) {
checkBadFlags(Flags.PARENTHESES, Flags.GROUP);
} else if (c == Conversion.SCIENTIFIC) {
checkBadFlags(Flags.GROUP);
} else if (c == Conversion.GENERAL) {
checkBadFlags(Flags.ALTERNATE);
}
}
private void checkNumeric() {
if (width != -1 && width < 0)
throw new IllegalFormatWidthException(width);
if (precision != -1 && precision < 0)
throw new IllegalFormatPrecisionException(precision);
// '-' and '0' require a width
if (width == -1
&& (f.contains(Flags.LEFT_JUSTIFY) || f
.contains(Flags.ZERO_PAD)))
throw new MissingFormatWidthException(toString());
// bad combination
if ((f.contains(Flags.PLUS) && f.contains(Flags.LEADING_SPACE))
|| (f.contains(Flags.LEFT_JUSTIFY) && f
.contains(Flags.ZERO_PAD)))
throw new IllegalFormatFlagsException(f.toString());
}
private void checkText() {
if (precision != -1)
throw new IllegalFormatPrecisionException(precision);
switch (c) {
case Conversion.PERCENT_SIGN:
if (f.valueOf() != Flags.LEFT_JUSTIFY.valueOf()
&& f.valueOf() != Flags.NONE.valueOf())
throw new IllegalFormatFlagsException(f.toString());
// '-' requires a width
if (width == -1 && f.contains(Flags.LEFT_JUSTIFY))
throw new MissingFormatWidthException(toString());
break;
case Conversion.LINE_SEPARATOR:
if (width != -1)
throw new IllegalFormatWidthException(width);
if (f.valueOf() != Flags.NONE.valueOf())
throw new IllegalFormatFlagsException(f.toString());
break;
default:
throw new UnknownFormatConversionException(String.valueOf(c));
}
}
public void print(String arg, int argIndex)
throws IllegalFormatConversionException,
FormatFlagsConversionMismatchException {
try {
if (arg.charAt(0) == '[')
failConversion(arg);
if (dt) {
printDateTime(arg);
return;
}
switch (c) {
case Conversion.DECIMAL_INTEGER:
case Conversion.OCTAL_INTEGER:
case Conversion.HEXADECIMAL_INTEGER:
printInteger(arg);
break;
case Conversion.SCIENTIFIC:
case Conversion.GENERAL:
case Conversion.DECIMAL_FLOAT:
case Conversion.HEXADECIMAL_FLOAT:
printFloat(arg);
break;
case Conversion.CHARACTER:
case Conversion.CHARACTER_UPPER:
printCharacter(arg);
break;
case Conversion.BOOLEAN:
printBoolean(arg);
break;
case Conversion.STRING:
case Conversion.HASHCODE:
case Conversion.LINE_SEPARATOR:
case Conversion.PERCENT_SIGN:
break;
default:
throw new UnknownFormatConversionException(String.valueOf(c));
}
} catch (IllegalFormatConversionException e) {
e.setArgIndex(argIndex);
throw e;
}
}
private boolean matchSig(String signature, Class<?>... classes) {
for (Class<?> c : classes)
if (matchSig(signature, c))
return true;
return false;
}
private boolean matchSig(String signature, Class<?> c) {
return signature.equals("L" + c.getName().replace('.', '/') + ";");
}
private boolean mightBeUnknown(String arg) {
if (matchSig(arg, Object.class) || matchSig(arg, Number.class)
|| matchSig(arg, Serializable.class)
|| matchSig(arg, Comparable.class))
return true;
return false;
}
private void printInteger(String arg)
throws IllegalFormatConversionException,
FormatFlagsConversionMismatchException {
if (mightBeUnknown(arg))
return;
if (matchSig(arg, Byte.class, Short.class, Integer.class, Long.class))
printLong();
else if (matchSig(arg, BigInteger.class)) {
} else
failConversion(arg);
}
private void printFloat(String arg) throws IllegalFormatConversionException {
if (mightBeUnknown(arg))
return;
if (matchSig(arg, Float.class, Double.class)) {
} else if (matchSig(arg, BigDecimal.class)) {
printBigDecimal(arg);
} else
failConversion(arg);
}
private void printDateTime(String arg)
throws IllegalFormatConversionException {
if (mightBeUnknown(arg))
return;
if (matchSig(arg, Long.class, Date.class, java.sql.Date.class,
java.sql.Time.class, java.sql.Timestamp.class, Calendar.class,
GregorianCalendar.class)) {
} else {
failConversion(arg);
}
}
private void printCharacter(String arg)
throws IllegalFormatConversionException {
if (mightBeUnknown(arg))
return;
if (matchSig(arg, Character.class, Byte.class, Short.class,
Integer.class)) {
} else {
failConversion(arg);
}
}
private void printBoolean(String arg)
throws IllegalFormatConversionException {
if (mightBeUnknown(arg))
return;
if (matchSig(arg, Boolean.class))
return;
failConversion(arg);
}
private void printLong() throws FormatFlagsConversionMismatchException {
if (c == Conversion.OCTAL_INTEGER) {
checkBadFlags(Flags.PARENTHESES, Flags.LEADING_SPACE, Flags.PLUS);
} else if (c == Conversion.HEXADECIMAL_INTEGER) {
checkBadFlags(Flags.PARENTHESES, Flags.LEADING_SPACE, Flags.PLUS);
}
}
private void printBigDecimal(String arg)
throws IllegalFormatConversionException {
if (c == Conversion.HEXADECIMAL_FLOAT)
failConversion(arg);
}
private void failMismatch(Flags f, char c)
throws FormatFlagsConversionMismatchException {
String fs = f.toString();
throw new FormatFlagsConversionMismatchException(fs, c);
}
private void failConversion(String arg)
throws IllegalFormatConversionException {
if (dt)
throw new IllegalFormatConversionException(this.toString(),
f.contains(Flags.UPPERCASE) ? 'T' : 't', arg);
throw new IllegalFormatConversionException(this.toString(), c, arg);
}
}