diff --git a/src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java b/src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java index 5462b6c5f..236abe859 100644 --- a/src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java +++ b/src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java @@ -275,11 +275,7 @@ public static String printDecimal(BigDecimal value) { // ======================== integer ======================== public static BigInteger lexInteger(CharSequence cs) throws NumberFormatException { - if (cs.length() > 1) { - if (cs.charAt(0) == '+' && cs.charAt(1) == '-') { - throw new NumberFormatException("Illegal char sequence '+-'"); - } - } + rejectSignAfterPlus(cs); final String v = cs.toString(); //TODO: consider special casing zero and one to return static values @@ -304,10 +300,25 @@ public static String printInteger(BigInteger value) { // ======================== long ======================== public static long lexLong(CharSequence cs) throws NumberFormatException { + rejectSignAfterPlus(cs); final String v = cs.toString(); return Long.parseLong(trimInitialPlus(v)); } + // trimInitialPlus drops a single leading '+', then Long.parseLong / + // new BigInteger accept their own leading sign, so "++5" and "+-5" slip + // through as 5 and -5. Neither is in the xsd integer lexical space + // ([\-+]?[0-9]+ allows one sign). lexInt/lexShort/lexByte already reject + // the second sign in parseIntXsdNumber. + private static void rejectSignAfterPlus(CharSequence cs) { + if (cs.length() > 1 && cs.charAt(0) == '+') { + final char c = cs.charAt(1); + if (c == '+' || c == '-') { + throw new NumberFormatException("Illegal char sequence '+" + c + "'"); + } + } + } + public static long lexLong(CharSequence cs, Collection errors) { try { return lexLong(cs); @@ -432,7 +443,7 @@ public static boolean lexBoolean(CharSequence value, Collection errors } public static String printBoolean(boolean value) { - return (value ? "true" : "false"); + return Boolean.toString(value); } @@ -469,7 +480,7 @@ public static QName lexQName(CharSequence charSeq, NamespaceContext nscontext) { String uri = nscontext.getNamespaceURI(prefix); if (uri == null) { - if (prefix != null && prefix.length() > 0) { + if (prefix != null && !prefix.isEmpty()) { throw new InvalidLexicalValueException("Can't resolve prefix: " + prefix); } @@ -495,7 +506,7 @@ public static String printQName(QName qname, NamespaceContext nsContext, final String uri = qname.getNamespaceURI(); assert uri != null; //qname is not allowed to have null uri values final String prefix; - if (uri.length() > 0) { + if (!uri.isEmpty()) { prefix = nsContext.getPrefix(uri); if (prefix == null) { String msg = "NamespaceContext does not provide" + @@ -513,9 +524,9 @@ public static String getQNameString(String uri, String localpart, String prefix) { if (prefix != null && - uri != null && - uri.length() > 0 && - prefix.length() > 0) { + uri != null && + !uri.isEmpty() && + !prefix.isEmpty()) { return (prefix + NAMESPACE_SEP + localpart); } else { return localpart; @@ -587,8 +598,7 @@ public static GDateSpecification getGDateValue(Date d, int builtin_type_code) { GDateBuilder gDateBuilder = new GDateBuilder(d); gDateBuilder.setBuiltinTypeCode(builtin_type_code); - GDate value = gDateBuilder.toGDate(); - return value; + return gDateBuilder.toGDate(); } @@ -596,20 +606,18 @@ public static GDateSpecification getGDateValue(Calendar c, int builtin_type_code) { GDateBuilder gDateBuilder = new GDateBuilder(c); gDateBuilder.setBuiltinTypeCode(builtin_type_code); - GDate value = gDateBuilder.toGDate(); - return value; + return gDateBuilder.toGDate(); } public static GDateSpecification getGDateValue(CharSequence v, int builtin_type_code) { GDateBuilder gDateBuilder = new GDateBuilder(v); gDateBuilder.setBuiltinTypeCode(builtin_type_code); - GDate value = gDateBuilder.toGDate(); - return value; + return gDateBuilder.toGDate(); } private static String trimInitialPlus(String xml) { - if (xml.length() > 0 && xml.charAt(0) == '+') { + if (!xml.isEmpty() && xml.charAt(0) == '+') { return xml.substring(1); } else { return xml; diff --git a/src/test/java/misc/checkin/XsTypeConverterTest.java b/src/test/java/misc/checkin/XsTypeConverterTest.java index 702d2233c..18a6b085e 100644 --- a/src/test/java/misc/checkin/XsTypeConverterTest.java +++ b/src/test/java/misc/checkin/XsTypeConverterTest.java @@ -166,6 +166,25 @@ void loadStrictFloatingPointOptionGatesDoubleParsing() throws Exception { XmlDouble.Factory.parse("0x1p4", strict).getDoubleValue()); } + @Test + void lexLongRejectsDoubleSign() { + // trimInitialPlus drops the leading '+', then Long.parseLong accepts its + // own sign, so "++5"/"+-5" used to parse as 5/-5 instead of being rejected. + assertEquals(5L, XsTypeConverter.lexLong("+5")); + assertEquals(-5L, XsTypeConverter.lexLong("-5")); + assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexLong("++5")); + assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexLong("+-5")); + } + + @Test + void lexIntegerRejectsDoubleSign() { + // "+-5" was already caught; "++5" leaked through as 5. + assertEquals(java.math.BigInteger.valueOf(5), XsTypeConverter.lexInteger("+5")); + assertEquals(java.math.BigInteger.valueOf(-5), XsTypeConverter.lexInteger("-5")); + assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexInteger("++5")); + assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexInteger("+-5")); + } + @Test void lexDecimalRejectsEmptyString() { // an empty value used to read charAt(-1) in trimTrailingZeros and