| /* |
| * 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); |
| } |
| |
| } |