diff options
6 files changed, 226 insertions, 38 deletions
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StringUtilsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StringUtilsTest.java index 82c0afec5f..aa7247e105 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StringUtilsTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StringUtilsTest.java @@ -12,6 +12,7 @@ package org.eclipse.jgit.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import org.junit.Test; @@ -70,4 +71,86 @@ public class StringUtilsTest { assertEquals("a b c d", StringUtils.replaceLineBreaksWithSpace("a\r\nb\nc d")); } + + @Test + public void testFormatWithSuffix() { + assertEquals("1023", StringUtils.formatWithSuffix(1023)); + assertEquals("1k", StringUtils.formatWithSuffix(1024)); + assertEquals("1025", StringUtils.formatWithSuffix(1025)); + assertEquals("1048575", StringUtils.formatWithSuffix(1024 * 1024 - 1)); + assertEquals("1m", StringUtils.formatWithSuffix(1024 * 1024)); + assertEquals("1048577", StringUtils.formatWithSuffix(1024 * 1024 + 1)); + assertEquals("1073741823", + StringUtils.formatWithSuffix(1024 * 1024 * 1024 - 1)); + assertEquals("1g", StringUtils.formatWithSuffix(1024 * 1024 * 1024)); + assertEquals("1073741825", + StringUtils.formatWithSuffix(1024 * 1024 * 1024 + 1)); + assertEquals("3k", StringUtils.formatWithSuffix(3 * 1024)); + assertEquals("3m", StringUtils.formatWithSuffix(3 * 1024 * 1024)); + assertEquals("2050k", + StringUtils.formatWithSuffix(2 * 1024 * 1024 + 2048)); + assertEquals("3g", + StringUtils.formatWithSuffix(3L * 1024 * 1024 * 1024)); + assertEquals("3000", StringUtils.formatWithSuffix(3000)); + assertEquals("3000000", StringUtils.formatWithSuffix(3_000_000)); + assertEquals("1953125k", StringUtils.formatWithSuffix(2_000_000_000)); + assertEquals("2000000010", StringUtils.formatWithSuffix(2_000_000_010)); + assertEquals("3000000000", + StringUtils.formatWithSuffix(3_000_000_000L)); + } + + @Test + public void testParseWithSuffix() { + assertEquals(1024, StringUtils.parseIntWithSuffix("1k", true)); + assertEquals(1024, StringUtils.parseIntWithSuffix("1 k", true)); + assertEquals(1024, StringUtils.parseIntWithSuffix("1 k", true)); + assertEquals(1024, StringUtils.parseIntWithSuffix(" \t1 k \n", true)); + assertEquals(1024, StringUtils.parseIntWithSuffix("1k", false)); + assertEquals(1024, StringUtils.parseIntWithSuffix("1K", false)); + assertEquals(1024 * 1024, StringUtils.parseIntWithSuffix("1m", false)); + assertEquals(1024 * 1024, StringUtils.parseIntWithSuffix("1M", false)); + assertEquals(-1024 * 1024, + StringUtils.parseIntWithSuffix("-1M", false)); + assertEquals(1_000_000, + StringUtils.parseIntWithSuffix(" 1000000\r\n", false)); + assertEquals(1024 * 1024 * 1024, + StringUtils.parseIntWithSuffix("1g", false)); + assertEquals(1024 * 1024 * 1024, + StringUtils.parseIntWithSuffix("1G", false)); + assertEquals(3L * 1024 * 1024 * 1024, + StringUtils.parseLongWithSuffix("3g", false)); + assertEquals(3L * 1024 * 1024 * 1024, + StringUtils.parseLongWithSuffix("3G", false)); + assertThrows(NumberFormatException.class, + () -> StringUtils.parseIntWithSuffix("2G", false)); + assertEquals(2L * 1024 * 1024 * 1024, + StringUtils.parseLongWithSuffix("2G", false)); + assertThrows(NumberFormatException.class, + () -> StringUtils.parseLongWithSuffix("-1m", true)); + assertThrows(NumberFormatException.class, + () -> StringUtils.parseLongWithSuffix("-1000", true)); + assertThrows(StringIndexOutOfBoundsException.class, + () -> StringUtils.parseLongWithSuffix("", false)); + assertThrows(StringIndexOutOfBoundsException.class, + () -> StringUtils.parseLongWithSuffix(" \t \n", false)); + assertThrows(StringIndexOutOfBoundsException.class, + () -> StringUtils.parseLongWithSuffix("k", false)); + assertThrows(StringIndexOutOfBoundsException.class, + () -> StringUtils.parseLongWithSuffix("m", false)); + assertThrows(StringIndexOutOfBoundsException.class, + () -> StringUtils.parseLongWithSuffix("g", false)); + assertThrows(NumberFormatException.class, + () -> StringUtils.parseLongWithSuffix("1T", false)); + assertThrows(NumberFormatException.class, + () -> StringUtils.parseLongWithSuffix("1t", false)); + assertThrows(NumberFormatException.class, + () -> StringUtils.parseLongWithSuffix("Nonumber", false)); + assertThrows(NumberFormatException.class, + () -> StringUtils.parseLongWithSuffix("0x001f", false)); + assertThrows(NumberFormatException.class, + () -> StringUtils.parseLongWithSuffix("beef", false)); + assertThrows(NumberFormatException.class, + () -> StringUtils.parseLongWithSuffix("8000000000000000000G", + false)); + } } diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index 74762a9021..ee97c265e9 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -793,6 +793,7 @@ uriNotFoundWithMessage={0} not found: {1} URINotSupported=URI not supported: {0} userConfigInvalid=Git config in the user's home directory {0} is invalid {1} validatingGitModules=Validating .gitmodules files +valueExceedsRange=Value ''{0}'' exceeds the range of {1} verifySignatureBad=BAD signature from "{0}" verifySignatureExpired=Expired signature from "{0}" verifySignatureGood=Good signature from "{0}" diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java index 3d5d0607e7..f7ebe4f40f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -821,6 +821,7 @@ public class JGitText extends TranslationBundle { /***/ public String URINotSupported; /***/ public String userConfigInvalid; /***/ public String validatingGitModules; + /***/ public String valueExceedsRange; /***/ public String verifySignatureBad; /***/ public String verifySignatureExpired; /***/ public String verifySignatureGood; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java index a369026c97..1ce3e312e2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java @@ -42,6 +42,7 @@ import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.RawParseUtils; +import org.eclipse.jgit.util.StringUtils; /** * Git style {@code .config}, {@code .gitconfig}, {@code .gitmodules} file. @@ -50,9 +51,6 @@ public class Config { private static final String[] EMPTY_STRING_ARRAY = {}; - static final long KiB = 1024; - static final long MiB = 1024 * KiB; - static final long GiB = 1024 * MiB; private static final int MAX_DEPTH = 10; private static final TypedConfigGetter DEFAULT_GETTER = new DefaultTypedConfigGetter(); @@ -765,18 +763,8 @@ public class Config { */ public void setLong(final String section, final String subsection, final String name, final long value) { - final String s; - - if (value >= GiB && (value % GiB) == 0) - s = String.valueOf(value / GiB) + "g"; //$NON-NLS-1$ - else if (value >= MiB && (value % MiB) == 0) - s = String.valueOf(value / MiB) + "m"; //$NON-NLS-1$ - else if (value >= KiB && (value % KiB) == 0) - s = String.valueOf(value / KiB) + "k"; //$NON-NLS-1$ - else - s = String.valueOf(value); - - setString(section, subsection, name, s); + setString(section, subsection, name, + StringUtils.formatWithSuffix(value)); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java index cc0b995f10..9f96bce251 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java @@ -126,30 +126,11 @@ public class DefaultTypedConfigGetter implements TypedConfigGetter { if (str == null) { return defaultValue; } - String n = str.trim(); - if (n.length() == 0) { - return defaultValue; - } - long mul = 1; - switch (StringUtils.toLowerCase(n.charAt(n.length() - 1))) { - case 'g': - mul = Config.GiB; - break; - case 'm': - mul = Config.MiB; - break; - case 'k': - mul = Config.KiB; - break; - } - if (mul > 1) { - n = n.substring(0, n.length() - 1).trim(); - } - if (n.length() == 0) { - return defaultValue; - } try { - return mul * Long.parseLong(n); + return StringUtils.parseLongWithSuffix(str, false); + } catch (StringIndexOutOfBoundsException e) { + // Empty + return defaultValue; } catch (NumberFormatException nfe) { throw new IllegalArgumentException(MessageFormat.format( JGitText.get().invalidIntegerValue, section, name, str), diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java index 61de65cac1..b77fb920ed 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java @@ -13,12 +13,20 @@ package org.eclipse.jgit.util; import java.text.MessageFormat; import java.util.Collection; +import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.internal.JGitText; /** * Miscellaneous string comparison utility methods. */ public final class StringUtils { + + private static final long KiB = 1024; + + private static final long MiB = 1024 * KiB; + + private static final long GiB = 1024 * MiB; + private static final char[] LC; static { @@ -307,4 +315,130 @@ public final class StringUtils { } return new String(buf, 0, o); } + + /** + * Parses a number with optional case-insensitive suffix 'k', 'm', or 'g' + * indicating KiB, MiB, and GiB, respectively. The suffix may follow the + * number with optional separation by one or more blanks. + * + * @param value + * {@link String} to parse; with leading and trailing whitespace + * ignored + * @param positiveOnly + * {@code true} to only accept positive numbers, {@code false} to + * allow negative numbers, too + * @return the value parsed + * @throws NumberFormatException + * if the {@value} is not parseable, or beyond the range of + * {@link Long} + * @throws StringIndexOutOfBoundsException + * if the string is empty or contains only whitespace, or + * contains only the letter 'k', 'm', or 'g' + * @since 6.0 + */ + public static long parseLongWithSuffix(@NonNull String value, + boolean positiveOnly) + throws NumberFormatException, StringIndexOutOfBoundsException { + String n = value.strip(); + if (n.isEmpty()) { + throw new StringIndexOutOfBoundsException(); + } + long mul = 1; + switch (n.charAt(n.length() - 1)) { + case 'g': + case 'G': + mul = GiB; + break; + case 'm': + case 'M': + mul = MiB; + break; + case 'k': + case 'K': + mul = KiB; + break; + default: + break; + } + if (mul > 1) { + n = n.substring(0, n.length() - 1).trim(); + } + if (n.isEmpty()) { + throw new StringIndexOutOfBoundsException(); + } + long number; + if (positiveOnly) { + number = Long.parseUnsignedLong(n); + if (number < 0) { + throw new NumberFormatException( + MessageFormat.format(JGitText.get().valueExceedsRange, + value, Long.class.getSimpleName())); + } + } else { + number = Long.parseLong(n); + } + if (mul == 1) { + return number; + } + try { + return Math.multiplyExact(mul, number); + } catch (ArithmeticException e) { + throw new NumberFormatException(e.getLocalizedMessage()); + } + } + + /** + * Parses a number with optional case-insensitive suffix 'k', 'm', or 'g' + * indicating KiB, MiB, and GiB, respectively. The suffix may follow the + * number with optional separation by blanks. + * + * @param value + * {@link String} to parse; with leading and trailing whitespace + * ignored + * @param positiveOnly + * {@code true} to only accept positive numbers, {@code false} to + * allow negative numbers, too + * @return the value parsed + * @throws NumberFormatException + * if the {@value} is not parseable or beyond the range of + * {@link Integer} + * @throws StringIndexOutOfBoundsException + * if the string is empty or contains only whitespace, or + * contains only the letter 'k', 'm', or 'g' + * @since 6.0 + */ + public static int parseIntWithSuffix(@NonNull String value, + boolean positiveOnly) + throws NumberFormatException, StringIndexOutOfBoundsException { + try { + return Math.toIntExact(parseLongWithSuffix(value, positiveOnly)); + } catch (ArithmeticException e) { + throw new NumberFormatException( + MessageFormat.format(JGitText.get().valueExceedsRange, + value, Integer.class.getSimpleName())); + } + } + + /** + * Formats an integral value as a decimal number with 'k', 'm', or 'g' + * suffix if it is an exact multiple of 1024, otherwise returns the value + * representation as a decimal number without suffix. + * + * @param value + * Value to format + * @return the value's String representation + * @since 6.0 + */ + public static String formatWithSuffix(long value) { + if (value >= GiB && (value % GiB) == 0) { + return String.valueOf(value / GiB) + 'g'; + } + if (value >= MiB && (value % MiB) == 0) { + return String.valueOf(value / MiB) + 'm'; + } + if (value >= KiB && (value % KiB) == 0) { + return String.valueOf(value / KiB) + 'k'; + } + return String.valueOf(value); + } } |