All Downloads are FREE. Search and download functionalities are using the official Maven repository.

checker.src.org.checkerframework.checker.formatter.FormatUtil Maven / Gradle / Ivy

Go to download

The Checker Framework enhances Java’s type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.42.0
Show newest version
package org.checkerframework.checker.formatter;

import org.checkerframework.checker.formatter.qual.ConversionCategory;
import org.checkerframework.checker.formatter.qual.ReturnsFormat;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.IllegalFormatConversionException;
import java.util.IllegalFormatException;
import java.util.Map;
import java.util.MissingFormatArgumentException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This class provides a collection of utilities to ease working
 * with format strings.
 *
 * @author Konstantin Weitz
 */
public class FormatUtil {
    private static class Conversion {
        private final int index;
        private final ConversionCategory cath;

        public Conversion(char c, int index) {
            this.index = index;
            this.cath = ConversionCategory.fromConversionChar(c);
        }

        int index() {
            return index;
        }

        ConversionCategory category() {
            return cath;
        }
    }

    /**
     * Returns if the format string is satisfiable, and if the
     * format's parameters match the passed {@link ConversionCategory}s.
     * Otherwise an {@link Error} is thrown.
     *
     * TODO introduce more such functions, see RegexUtil for examples
     */
    @ReturnsFormat
    public static String asFormat(String format, ConversionCategory... cc) throws IllegalFormatException {
        ConversionCategory[] fcc = formatParameterCategories(format);
        if (fcc.length != cc.length) {
            throw new ExcessiveOrMissingFormatArgumentException(cc.length, fcc.length);
        }

        for (int i = 0; i < cc.length; i++) {
            if (cc[i] != fcc[i]) {
                throw new IllegalFormatConversionCategoryException(cc[i], fcc[i]);
            }
        }

        return format;
    }

    /**
     * Throws an exception if the format is not syntactically valid.
     */
    public static void tryFormatSatisfiability(String format) throws IllegalFormatException {
        String.format(format, (Object[])null);
    }

    /**
     * Returns a {@link ConversionCategory} for every conversion found in the
     * format string.
     *
     * Throws an exception if the format is not syntactically valid.
     */
    public static ConversionCategory[] formatParameterCategories(String format) throws IllegalFormatException {
        tryFormatSatisfiability(format);

        int last = -1;        // index of last argument referenced
        int lasto = -1;       // last ordinary index
        int maxindex = -1;

        Conversion[] cs = parse(format);
        Map conv = new HashMap();

        for (Conversion c : cs) {
            int index = c.index();
            switch (index) {
            case -1:  // relative index
                break;
            case 0:  // ordinary index
                lasto++;
                last = lasto;
                break;
            default:  // explicit index
                last = index - 1;
                break;
            }
            maxindex = Math.max(maxindex, last);
            conv.put(last, ConversionCategory.intersect(
                    conv.containsKey(last) ? conv.get(last) : ConversionCategory.UNUSED,
                    c.category()));
        }

        ConversionCategory[] res = new ConversionCategory[maxindex+1];
        for (int i = 0; i <= maxindex ;++i) {
            res[i] = conv.containsKey(i)?conv.get(i):ConversionCategory.UNUSED;
        }
        return res;
    }

    // %[argument_index$][flags][width][.precision][t]conversion
    private static final String formatSpecifier
        = "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";

    private static Pattern fsPattern = Pattern.compile(formatSpecifier);

    private static int indexFromFormat(Matcher m) {
        int index;
        String s = m.group(1);
        if (s != null) {  // explicit index
            index = Integer.parseInt(s.substring(0, s.length() - 1));
        } else {
            if (m.group(2) != null && m.group(2).contains(String.valueOf('<'))) {
                index = -1;    // relative index
            } else {
                index = 0;    // ordinary index
            }
        }
        return index;
    }

    private static char conversionCharFromFormat(Matcher m) {
        String dt = m.group(5);
        if (dt == null) {
            return m.group(6).charAt(0);
        } else {
            return dt.charAt(0);
        }
    }

    private static Conversion[] parse(String format) {
        ArrayList cs = new ArrayList();
        Matcher m = fsPattern.matcher(format);
        while (m.find()) {
            char c = conversionCharFromFormat(m);
            switch (c) {
            case '%':
            case 'n':
                break;
            default:
                cs.add(new Conversion(c, indexFromFormat(m)));
            }
        }
        return cs.toArray(new Conversion[cs.size()]);
    }

    public static class ExcessiveOrMissingFormatArgumentException extends MissingFormatArgumentException {
        private static final long serialVersionUID = 17000126L;

        private final int expected;
        private final int found;

        /**
         * Constructs an instance of this class with the actual argument length and
         * the expected one.
         */
        public ExcessiveOrMissingFormatArgumentException(int expected, int found) {
            super("-");
            this.expected = expected;
            this.found = found;
        }

        public int getExpected() {
            return expected;
        }

        public int getFound() {
            return found;
        }

        @Override
        public String getMessage() {
            return String.format("Expected %d arguments but found %d.", expected, found);
        }
    }

    public static class IllegalFormatConversionCategoryException extends IllegalFormatConversionException {
        private static final long serialVersionUID = 17000126L;

        private final ConversionCategory expected;
        private final ConversionCategory found;

        /**
         * Constructs an instance of this class with the mismatched conversion and
         * the expected one.
         */
        public IllegalFormatConversionCategoryException(ConversionCategory expected,
                ConversionCategory found) {
            super(expected.chars.length() == 0 ? '-' : expected.chars.charAt(0),
                  found.types == null ? Object.class : found.types[0]);
            this.expected = expected;
            this.found = found;
        }

        public ConversionCategory getExpected() {
            return expected;
        }

        public ConversionCategory getFound() {
            return found;
        }

        @Override
        public String getMessage() {
            return String.format("Expected category %s but found %s.", expected, found);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy