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

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

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

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;
import org.checkerframework.checker.formatter.qual.ConversionCategory;
import org.checkerframework.checker.formatter.qual.ReturnsFormat;

/** This class provides a collection of utilities to ease working with format strings. */
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 { @SuppressWarnings("unused") String unused = 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